254 lines
6.6 KiB
PHP
254 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace Kirby\Cms;
|
|
|
|
use Kirby\Filesystem\F;
|
|
use Kirby\Filesystem\Mime;
|
|
use Kirby\Toolkit\Str;
|
|
|
|
/**
|
|
* Extension of the basic blueprint class
|
|
* to handle all blueprints for files.
|
|
*
|
|
* @package Kirby Cms
|
|
* @author Bastian Allgeier <bastian@getkirby.com>
|
|
* @link https://getkirby.com
|
|
* @copyright Bastian Allgeier
|
|
* @license https://getkirby.com/license
|
|
*/
|
|
class FileBlueprint extends Blueprint
|
|
{
|
|
/**
|
|
* `true` if the default accepted
|
|
* types are being used
|
|
*/
|
|
protected bool $defaultTypes = false;
|
|
|
|
public function __construct(array $props)
|
|
{
|
|
parent::__construct($props);
|
|
|
|
// normalize all available page options
|
|
$this->props['options'] = $this->normalizeOptions(
|
|
$this->props['options'] ?? true,
|
|
// defaults
|
|
[
|
|
'access' => null,
|
|
'changeName' => null,
|
|
'changeTemplate' => null,
|
|
'create' => null,
|
|
'delete' => null,
|
|
'list' => null,
|
|
'read' => null,
|
|
'replace' => null,
|
|
'update' => null,
|
|
]
|
|
);
|
|
|
|
// normalize the accept settings
|
|
$this->props['accept'] = $this->normalizeAccept($this->props['accept'] ?? []);
|
|
}
|
|
|
|
public function accept(): array
|
|
{
|
|
return $this->props['accept'];
|
|
}
|
|
|
|
/**
|
|
* Returns the list of all accepted MIME types for
|
|
* file upload or `*` if all MIME types are allowed
|
|
*
|
|
* @deprecated 4.2.0 Use `acceptAttribute` instead
|
|
* @todo 5.0.0 Remove method
|
|
*/
|
|
public function acceptMime(): string
|
|
{
|
|
// don't disclose the specific default types
|
|
if ($this->defaultTypes === true) {
|
|
return '*';
|
|
}
|
|
|
|
$accept = $this->accept();
|
|
$restrictions = [];
|
|
|
|
if (is_array($accept['mime']) === true) {
|
|
$restrictions[] = $accept['mime'];
|
|
} else {
|
|
// only fall back to the extension or type if
|
|
// no explicit MIME types were defined
|
|
// (allows to set custom MIME types for the frontend
|
|
// check but still restrict the extension and/or type)
|
|
|
|
if (is_array($accept['extension']) === true) {
|
|
// determine the main MIME type for each extension
|
|
$restrictions[] = array_map(
|
|
[Mime::class, 'fromExtension'],
|
|
$accept['extension']
|
|
);
|
|
}
|
|
|
|
if (is_array($accept['type']) === true) {
|
|
// determine the MIME types of each file type
|
|
$mimes = [];
|
|
foreach ($accept['type'] as $type) {
|
|
if ($extensions = F::typeToExtensions($type)) {
|
|
$mimes[] = array_map(
|
|
[Mime::class, 'fromExtension'],
|
|
$extensions
|
|
);
|
|
}
|
|
}
|
|
|
|
$restrictions[] = array_merge(...$mimes);
|
|
}
|
|
}
|
|
|
|
if ($restrictions !== []) {
|
|
if (count($restrictions) > 1) {
|
|
// only return the MIME types that are allowed by all restrictions
|
|
$mimes = array_intersect(...$restrictions);
|
|
} else {
|
|
$mimes = $restrictions[0];
|
|
}
|
|
|
|
// filter out empty MIME types and duplicates
|
|
return implode(', ', array_filter(array_unique($mimes)));
|
|
}
|
|
|
|
// no restrictions, accept everything
|
|
return '*';
|
|
}
|
|
|
|
/**
|
|
* Returns the list of all accepted file extensions
|
|
* for file upload or `*` if all extensions are allowed
|
|
*
|
|
* If a MIME type is specified in the blueprint, the `extension` and `type` options are ignored for the browser.
|
|
* Extensions and types, however, are still used to validate an uploaded file on the server.
|
|
* This behavior might change in the future to better represent which file extensions are actually allowed.
|
|
*
|
|
* If no MIME type is specified, the intersection between manually defined extensions and the Kirby "file types" is returned.
|
|
* If the intersection is empty, an empty string is returned.
|
|
* This behavior might change in the future to instead return the union of `mime`, `extension` and `type`.
|
|
*
|
|
* @since 4.2.0
|
|
*/
|
|
public function acceptAttribute(): string
|
|
{
|
|
// don't disclose the specific default types
|
|
if ($this->defaultTypes === true) {
|
|
return '*';
|
|
}
|
|
|
|
$accept = $this->accept();
|
|
|
|
// get extensions from "mime" option
|
|
if (is_array($accept['mime']) === true) {
|
|
// determine the extensions for each MIME type
|
|
$extensions = array_map(
|
|
fn ($pattern) => Mime::toExtensions($pattern, true),
|
|
$accept['mime']
|
|
);
|
|
|
|
$fromMime = array_unique(array_merge(...array_values($extensions)));
|
|
|
|
// return early to ignore the other options
|
|
return implode(',', array_map(fn ($ext) => ".$ext", $fromMime));
|
|
}
|
|
|
|
$restrictions = [];
|
|
|
|
// get extensions from "type" option
|
|
if (is_array($accept['type']) === true) {
|
|
$extensions = array_map(
|
|
fn ($type) => F::typeToExtensions($type) ?? [],
|
|
$accept['type']
|
|
);
|
|
|
|
$fromType = array_merge(...array_values($extensions));
|
|
$restrictions[] = $fromType;
|
|
}
|
|
|
|
// get extensions from "extension" option
|
|
if (is_array($accept['extension']) === true) {
|
|
$restrictions[] = $accept['extension'];
|
|
}
|
|
|
|
// intersect all restrictions
|
|
$list = match (count($restrictions)) {
|
|
0 => [],
|
|
1 => $restrictions[0],
|
|
default => array_intersect(...$restrictions)
|
|
};
|
|
|
|
$list = array_unique($list);
|
|
|
|
// format the list to include a leading dot on each extension
|
|
return implode(',', array_map(fn ($ext) => ".$ext", $list));
|
|
}
|
|
|
|
protected function normalizeAccept(mixed $accept = null): array
|
|
{
|
|
$accept = match (true) {
|
|
is_string($accept) => ['mime' => $accept],
|
|
// explicitly no restrictions at all
|
|
$accept === true => ['mime' => null],
|
|
// no custom restrictions
|
|
empty($accept) === true => [],
|
|
// custom restrictions
|
|
default => $accept
|
|
};
|
|
|
|
$accept = array_change_key_case($accept);
|
|
|
|
$defaults = [
|
|
'extension' => null,
|
|
'mime' => null,
|
|
'maxheight' => null,
|
|
'maxsize' => null,
|
|
'maxwidth' => null,
|
|
'minheight' => null,
|
|
'minsize' => null,
|
|
'minwidth' => null,
|
|
'orientation' => null,
|
|
'type' => null
|
|
];
|
|
|
|
// default type restriction if none are configured;
|
|
// this ensures that no unexpected files are uploaded
|
|
if (
|
|
array_key_exists('mime', $accept) === false &&
|
|
array_key_exists('extension', $accept) === false &&
|
|
array_key_exists('type', $accept) === false
|
|
) {
|
|
$defaults['type'] = ['image', 'document', 'archive', 'audio', 'video'];
|
|
$this->defaultTypes = true;
|
|
}
|
|
|
|
$accept = array_merge($defaults, $accept);
|
|
|
|
// normalize the MIME, extension and type from strings into arrays
|
|
if (is_string($accept['mime']) === true) {
|
|
$accept['mime'] = array_map(
|
|
fn ($mime) => $mime['value'],
|
|
Str::accepted($accept['mime'])
|
|
);
|
|
}
|
|
|
|
if (is_string($accept['extension']) === true) {
|
|
$accept['extension'] = array_map(
|
|
'trim',
|
|
explode(',', $accept['extension'])
|
|
);
|
|
}
|
|
|
|
if (is_string($accept['type']) === true) {
|
|
$accept['type'] = array_map(
|
|
'trim',
|
|
explode(',', $accept['type'])
|
|
);
|
|
}
|
|
|
|
return $accept;
|
|
}
|
|
}
|