julienmonnerie/kirby/src/Cms/FileBlueprint.php

255 lines
6.6 KiB
PHP
Raw Normal View History

2022-06-17 17:51:59 +02:00
<?php
namespace Kirby\Cms;
use Kirby\Filesystem\F;
2022-12-19 14:56:05 +01:00
use Kirby\Filesystem\Mime;
2022-06-17 17:51:59 +02:00
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
{
2022-08-31 15:02:43 +02:00
/**
* `true` if the default accepted
* types are being used
*/
2025-04-21 18:57:21 +02:00
protected bool $defaultTypes = false;
2022-08-31 15:02:43 +02:00
public function __construct(array $props)
{
parent::__construct($props);
// normalize all available page options
$this->props['options'] = $this->normalizeOptions(
$this->props['options'] ?? true,
// defaults
[
2025-04-21 18:57:21 +02:00
'access' => null,
'changeName' => null,
'changeTemplate' => null,
'create' => null,
'delete' => null,
'list' => null,
'read' => null,
'replace' => null,
'update' => null,
2022-08-31 15:02:43 +02:00
]
);
// 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
*
2025-04-21 18:57:21 +02:00
* @deprecated 4.2.0 Use `acceptAttribute` instead
* @todo 5.0.0 Remove method
2022-08-31 15:02:43 +02:00
*/
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
2022-12-19 14:56:05 +01:00
$restrictions[] = array_map(
[Mime::class, 'fromExtension'],
$accept['extension']
);
2022-08-31 15:02:43 +02:00
}
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)) {
2022-12-19 14:56:05 +01:00
$mimes[] = array_map(
[Mime::class, 'fromExtension'],
$extensions
);
2022-08-31 15:02:43 +02:00
}
}
$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 '*';
}
/**
2025-04-21 18:57:21 +02:00
* 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
2022-08-31 15:02:43 +02:00
*/
2025-04-21 18:57:21 +02:00
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
2022-08-31 15:02:43 +02:00
{
2022-12-19 14:56:05 +01:00
$accept = match (true) {
is_string($accept) => ['mime' => $accept],
2022-08-31 15:02:43 +02:00
// explicitly no restrictions at all
2022-12-19 14:56:05 +01:00
$accept === true => ['mime' => null],
2022-08-31 15:02:43 +02:00
// no custom restrictions
2022-12-19 14:56:05 +01:00
empty($accept) === true => [],
// custom restrictions
default => $accept
};
2022-08-31 15:02:43 +02:00
$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;
}
2022-06-17 17:51:59 +02:00
}