1
0

downgrade to kirby v3

This commit is contained in:
Philip Wagner
2024-09-01 10:47:15 +02:00
parent a4b2aece7b
commit af86acb7a1
1085 changed files with 54743 additions and 65042 deletions

View File

@@ -3,8 +3,6 @@
namespace Kirby\Filesystem;
use Kirby\Cms\FileModifications;
use Kirby\Cms\HasMethods;
use Kirby\Exception\BadMethodCallException;
/**
* Anything in your public path can be converted
@@ -22,53 +20,32 @@ class Asset
{
use IsFile;
use FileModifications;
use HasMethods;
/**
* Relative file path
*
* @var string
*/
protected string|null $path;
protected $path;
/**
* Creates a new Asset object for the given path.
*
* @param string $path
*/
public function __construct(string $path)
{
$this->root = $this->kirby()->root('index') . '/' . $path;
$this->url = $this->kirby()->url('base') . '/' . $path;
$path = dirname($path);
$this->path = $path === '.' ? '' : $path;
}
/**
* Magic caller for asset methods
*
* @throws \Kirby\Exception\BadMethodCallException
*/
public function __call(string $method, array $arguments = []): mixed
{
// public property access
if (isset($this->$method) === true) {
return $this->$method;
}
// asset method proxy
if (method_exists($this->asset(), $method)) {
return $this->asset()->$method(...$arguments);
}
// asset methods
if ($this->hasMethod($method)) {
return $this->callMethod($method, $arguments);
}
throw new BadMethodCallException('The method: "' . $method . '" does not exist');
$this->setProperties([
'path' => dirname($path),
'root' => $this->kirby()->root('index') . '/' . $path,
'url' => $this->kirby()->url('base') . '/' . $path
]);
}
/**
* Returns a unique id for the asset
*
* @return string
*/
public function id(): string
{
@@ -77,6 +54,8 @@ class Asset
/**
* Create a unique media hash
*
* @return string
*/
public function mediaHash(): string
{
@@ -85,6 +64,8 @@ class Asset
/**
* Returns the relative path starting at the media folder
*
* @return string
*/
public function mediaPath(): string
{
@@ -93,6 +74,8 @@ class Asset
/**
* Returns the absolute path to the file in the public media folder
*
* @return string
*/
public function mediaRoot(): string
{
@@ -101,6 +84,8 @@ class Asset
/**
* Returns the absolute Url to the file in the public media folder
*
* @return string
*/
public function mediaUrl(): string
{
@@ -110,9 +95,23 @@ class Asset
/**
* Returns the path of the file from the web root,
* excluding the filename
*
* @return string
*/
public function path(): string
{
return $this->path;
}
/**
* Setter for the path
*
* @param string $path
* @return $this
*/
protected function setPath(string $path)
{
$this->path = $path === '.' ? '' : $path;
return $this;
}
}

View File

@@ -4,7 +4,6 @@ namespace Kirby\Filesystem;
use Exception;
use Kirby\Cms\App;
use Kirby\Cms\Helpers;
use Kirby\Cms\Page;
use Kirby\Toolkit\Str;
use Throwable;
@@ -31,8 +30,10 @@ class Dir
{
/**
* Ignore when scanning directories
*
* @var array
*/
public static array $ignore = [
public static $ignore = [
'.',
'..',
'.DS_Store',
@@ -44,21 +45,19 @@ class Dir
'@eaDir'
];
public static string $numSeparator = '_';
public static $numSeparator = '_';
/**
* Copy the directory to a new destination
*
* @param array|false $ignore List of full paths to skip during copying
* or `false` to copy all files, including
* those listed in `Dir::$ignore`
* @param string $dir
* @param string $target
* @param bool $recursive
* @param array $ignore
* @return bool
*/
public static function copy(
string $dir,
string $target,
bool $recursive = true,
array|false $ignore = []
): bool {
public static function copy(string $dir, string $target, bool $recursive = true, array $ignore = []): bool
{
if (is_dir($dir) === false) {
throw new Exception('The directory "' . $dir . '" does not exist');
}
@@ -71,13 +70,10 @@ class Dir
throw new Exception('The target directory "' . $target . '" could not be created');
}
foreach (static::read($dir, $ignore === false ? [] : null) as $name) {
foreach (static::read($dir) as $name) {
$root = $dir . '/' . $name;
if (
is_array($ignore) === true &&
in_array($root, $ignore) === true
) {
if (in_array($root, $ignore) === true) {
continue;
}
@@ -95,14 +91,15 @@ class Dir
/**
* Get all subdirectories
*
* @param string $dir
* @param array $ignore
* @param bool $absolute
* @return array
*/
public static function dirs(
string $dir,
array|null $ignore = null,
bool $absolute = false
): array {
$scan = static::read($dir, $ignore, true);
$result = array_values(array_filter($scan, 'is_dir'));
public static function dirs(string $dir, array $ignore = null, bool $absolute = false): array
{
$result = array_values(array_filter(static::read($dir, $ignore, true), 'is_dir'));
if ($absolute !== true) {
$result = array_map('basename', $result);
@@ -113,6 +110,9 @@ class Dir
/**
* Checks if the directory exists on disk
*
* @param string $dir
* @return bool
*/
public static function exists(string $dir): bool
{
@@ -121,14 +121,15 @@ class Dir
/**
* Get all files
*
* @param string $dir
* @param array $ignore
* @param bool $absolute
* @return array
*/
public static function files(
string $dir,
array|null $ignore = null,
bool $absolute = false
): array {
$scan = static::read($dir, $ignore, true);
$result = array_values(array_filter($scan, 'is_file'));
public static function files(string $dir, array $ignore = null, bool $absolute = false): array
{
$result = array_values(array_filter(static::read($dir, $ignore, true), 'is_file'));
if ($absolute !== true) {
$result = array_map('basename', $result);
@@ -140,39 +141,25 @@ class Dir
/**
* Read the directory and all subdirectories
*
* @todo Remove support for `$ignore = null` in a major release
* @param array|false|null $ignore Array of absolut file paths;
* `false` to disable `Dir::$ignore` list
* (passing null is deprecated)
* @param string $dir
* @param bool $recursive
* @param array $ignore
* @param string $path
* @return array
*/
public static function index(
string $dir,
bool $recursive = false,
array|false|null $ignore = [],
string $path = null
): array {
public static function index(string $dir, bool $recursive = false, array $ignore = null, string $path = null)
{
$result = [];
$dir = realpath($dir);
$items = static::read($dir, $ignore === false ? [] : null);
$items = static::read($dir);
foreach ($items as $item) {
$root = $dir . '/' . $item;
if (
is_array($ignore) === true &&
in_array($root, $ignore) === true
) {
continue;
}
$root = $dir . '/' . $item;
$entry = $path !== null ? $path . '/' . $item : $item;
$result[] = $entry;
if ($recursive === true && is_dir($root) === true) {
$result = [
...$result,
...static::index($root, true, $ignore, $entry)
];
$result = array_merge($result, static::index($root, true, $ignore, $entry));
}
}
@@ -181,6 +168,9 @@ class Dir
/**
* Checks if the folder has any contents
*
* @param string $dir
* @return bool
*/
public static function isEmpty(string $dir): bool
{
@@ -189,6 +179,9 @@ class Dir
/**
* Checks if the directory is readable
*
* @param string $dir
* @return bool
*/
public static function isReadable(string $dir): bool
{
@@ -197,6 +190,9 @@ class Dir
/**
* Checks if the directory is writable
*
* @param string $dir
* @return bool
*/
public static function isWritable(string $dir): bool
{
@@ -213,156 +209,163 @@ class Dir
* Don't use outside the Cms context.
*
* @internal
*
* @param string $dir
* @param string $contentExtension
* @param array|null $contentIgnore
* @param bool $multilang
* @return array
*/
public static function inventory(
string $dir,
string $contentExtension = 'txt',
array|null $contentIgnore = null,
bool $multilang = false
): array {
public static function inventory(string $dir, string $contentExtension = 'txt', array $contentIgnore = null, bool $multilang = false): array
{
$dir = realpath($dir);
$inventory = [
'children' => [],
'files' => [],
'template' => 'default',
];
$dir = realpath($dir);
if ($dir === false) {
return $inventory;
}
$items = static::read($dir, $contentIgnore);
// a temporary store for all content files
$content = [];
// read and sort all items naturally to avoid sorting issues later
$items = static::read($dir, $contentIgnore);
// sort all items naturally to avoid sorting issues later
natsort($items);
// loop through all directory items and collect all relevant information
foreach ($items as $item) {
// ignore all items with a leading dot or underscore
// ignore all items with a leading dot
if (in_array(substr($item, 0, 1), ['.', '_']) === true) {
continue;
}
$root = $dir . '/' . $item;
// collect all directories as children
if (is_dir($root) === true) {
$inventory['children'][] = static::inventoryChild(
$item,
$root,
$contentExtension,
$multilang
);
continue;
}
$extension = pathinfo($item, PATHINFO_EXTENSION);
// don't track files with these extensions
if (in_array($extension, ['htm', 'html', 'php']) === true) {
continue;
}
// collect all content files separately,
// not as inventory entries
if ($extension === $contentExtension) {
$filename = pathinfo($item, PATHINFO_FILENAME);
// remove the language codes from all content filenames
if ($multilang === true) {
$filename = pathinfo($filename, PATHINFO_FILENAME);
// extract the slug and num of the directory
if (preg_match('/^([0-9]+)' . static::$numSeparator . '(.*)$/', $item, $match)) {
$num = (int)$match[1];
$slug = $match[2];
} else {
$num = null;
$slug = $item;
}
$content[] = $filename;
continue;
}
$inventory['children'][] = [
'dirname' => $item,
'model' => null,
'num' => $num,
'root' => $root,
'slug' => $slug,
];
} else {
$extension = pathinfo($item, PATHINFO_EXTENSION);
// collect all other files
$inventory['files'][$item] = [
'filename' => $item,
'extension' => $extension,
'root' => $root,
];
switch ($extension) {
case 'htm':
case 'html':
case 'php':
// don't track those files
break;
case $contentExtension:
$content[] = pathinfo($item, PATHINFO_FILENAME);
break;
default:
$inventory['files'][$item] = [
'filename' => $item,
'extension' => $extension,
'root' => $root,
];
}
}
}
$content = array_unique($content);
// remove the language codes from all content filenames
if ($multilang === true) {
foreach ($content as $key => $filename) {
$content[$key] = pathinfo($filename, PATHINFO_FILENAME);
}
$inventory['template'] = static::inventoryTemplate(
$content,
$inventory['files']
);
$content = array_unique($content);
}
$inventory = static::inventoryContent($inventory, $content);
$inventory = static::inventoryModels($inventory, $contentExtension, $multilang);
return $inventory;
}
/**
* Collect information for a child for the inventory
* Take all content files,
* remove those who are meta files and
* detect the main content file
*
* @param array $inventory
* @param array $content
* @return array
*/
protected static function inventoryChild(
string $item,
string $root,
string $contentExtension = 'txt',
bool $multilang = false
): array {
// extract the slug and num of the directory
if ($separator = strpos($item, static::$numSeparator)) {
$num = (int)substr($item, 0, $separator);
$slug = substr($item, $separator + 1);
protected static function inventoryContent(array $inventory, array $content): array
{
// filter meta files from the content file
if (empty($content) === true) {
$inventory['template'] = 'default';
return $inventory;
}
// determine the model
if (empty(Page::$models) === false) {
if ($multilang === true) {
$code = App::instance()->defaultLanguage()->code();
$contentExtension = $code . '.' . $contentExtension;
}
// look if a content file can be found
// for any of the available models
foreach (Page::$models as $modelName => $modelClass) {
if (is_file($root . '/' . $modelName . '.' . $contentExtension) === true) {
$model = $modelName;
break;
}
}
}
return [
'dirname' => $item,
'model' => $model ?? null,
'num' => $num ?? null,
'root' => $root,
'slug' => $slug ?? $item,
];
}
/**
* Determines the main template for the inventory
* from all collected content files, ignore file meta files
*/
protected static function inventoryTemplate(
array $content,
array $files,
): string {
foreach ($content as $name) {
// is a meta file corresponding to an actual file, i.e. cover.jpg
if (isset($files[$name]) === true) {
foreach ($content as $contentName) {
// could be a meta file. i.e. cover.jpg
if (isset($inventory['files'][$contentName]) === true) {
continue;
}
// it's most likely the template
// (will overwrite and use the last match for historic reasons)
$template = $name;
$inventory['template'] = $contentName;
}
return $template ?? 'default';
return $inventory;
}
/**
* Go through all inventory children
* and inject a model for each
*
* @param array $inventory
* @param string $contentExtension
* @param bool $multilang
* @return array
*/
protected static function inventoryModels(array $inventory, string $contentExtension, bool $multilang = false): array
{
// inject models
if (empty($inventory['children']) === false && empty(Page::$models) === false) {
if ($multilang === true) {
$contentExtension = App::instance()->defaultLanguage()->code() . '.' . $contentExtension;
}
foreach ($inventory['children'] as $key => $child) {
foreach (Page::$models as $modelName => $modelClass) {
if (file_exists($child['root'] . '/' . $modelName . '.' . $contentExtension) === true) {
$inventory['children'][$key]['model'] = $modelName;
break;
}
}
}
}
return $inventory;
}
/**
* Create a (symbolic) link to a directory
*
* @param string $source
* @param string $link
* @return bool
*/
public static function link(string $source, string $link): bool
{
@@ -378,7 +381,7 @@ class Dir
try {
return symlink($source, $link) === true;
} catch (Throwable) {
} catch (Throwable $e) {
return false;
}
}
@@ -407,21 +410,17 @@ class Dir
$parent = dirname($dir);
if ($recursive === true && is_dir($parent) === false) {
static::make($parent, true);
if ($recursive === true) {
if (is_dir($parent) === false) {
static::make($parent, true);
}
}
if (is_writable($parent) === false) {
throw new Exception(sprintf('The directory "%s" cannot be created', $dir));
}
return Helpers::handleErrors(
fn (): bool => mkdir($dir),
// if the dir was already created (race condition),
fn (int $errno, string $errstr): bool => Str::endsWith($errstr, 'File exists'),
// consider it a success
true
);
return mkdir($dir);
}
/**
@@ -429,22 +428,22 @@ class Dir
* subfolders have been modified for the last time.
*
* @param string $dir The path of the directory
* @param 'date'|'intl'|'strftime'|null $handler Custom date handler or `null`
* for the globally configured one
* @param string $format
* @param string $handler
* @return int|string
*/
public static function modified(
string $dir,
string $format = null,
string|null $handler = null
): int|string {
public static function modified(string $dir, string $format = null, string $handler = 'date')
{
$modified = filemtime($dir);
$items = static::read($dir);
foreach ($items as $item) {
$newModified = match (is_file($dir . '/' . $item)) {
true => filemtime($dir . '/' . $item),
false => static::modified($dir . '/' . $item)
};
if (is_file($dir . '/' . $item) === true) {
$newModified = filemtime($dir . '/' . $item);
} else {
$newModified = static::modified($dir . '/' . $item);
}
$modified = ($newModified > $modified) ? $newModified : $modified;
}
@@ -479,14 +478,13 @@ class Dir
* Returns a nicely formatted size of all the contents of the folder
*
* @param string $dir The path of the directory
* @param string|false|null $locale Locale for number formatting,
* @param string|null|false $locale Locale for number formatting,
* `null` for the current locale,
* `false` to disable number formatting
* @return mixed
*/
public static function niceSize(
string $dir,
string|false|null $locale = null
): string {
public static function niceSize(string $dir, $locale = null)
{
return F::niceSize(static::size($dir), $locale);
}
@@ -499,11 +497,8 @@ class Dir
* @param bool $absolute If true, the full path for each item will be returned
* @return array An array of filenames
*/
public static function read(
string $dir,
array|null $ignore = null,
bool $absolute = false
): array {
public static function read(string $dir, array $ignore = null, bool $absolute = false): array
{
if (is_dir($dir) === false) {
return [];
}
@@ -525,6 +520,9 @@ class Dir
/**
* Removes a folder including all containing files and folders
*
* @param string $dir
* @return bool
*/
public static function remove(string $dir): bool
{
@@ -560,8 +558,9 @@ class Dir
*
* @param string $dir The path of the directory
* @param bool $recursive Include all subfolders and their files
* @return mixed
*/
public static function size(string $dir, bool $recursive = true): int|false
public static function size(string $dir, bool $recursive = true)
{
if (is_dir($dir) === false) {
return false;
@@ -583,6 +582,10 @@ class Dir
/**
* Checks if the directory or any subdirectory has been
* modified after the given timestamp
*
* @param string $dir
* @param int $time
* @return bool
*/
public static function wasModifiedAfter(string $dir, int $time): bool
{
@@ -599,10 +602,7 @@ class Dir
return true;
}
if (
is_dir($subdir) === true &&
static::wasModifiedAfter($subdir, $time) === true
) {
if (is_dir($subdir) === true && static::wasModifiedAfter($subdir, $time) === true) {
return true;
}
}

View File

@@ -3,9 +3,7 @@
namespace Kirby\Filesystem;
use Exception;
use IntlDateFormatter;
use Kirby\Cms\Helpers;
use Kirby\Http\Response;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
use Throwable;
@@ -25,7 +23,10 @@ use ZipArchive;
*/
class F
{
public static array $types = [
/**
* @var array
*/
public static $types = [
'archive' => [
'gz',
'gzip',
@@ -110,23 +111,17 @@ class F
],
];
public static array $units = [
'B',
'KB',
'MB',
'GB',
'TB',
'PB',
'EB',
'ZB',
'YB'
];
/**
* @var array
*/
public static $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
/**
* Appends new content to an existing file
*
* @param string $file The path for the file
* @param mixed $content Either a string or an array. Arrays will be converted to JSON.
* @return bool
*/
public static function append(string $file, $content): bool
{
@@ -137,6 +132,7 @@ class F
* Returns the file content as base64 encoded string
*
* @param string $file The path for the file
* @return string
*/
public static function base64(string $file): string
{
@@ -145,6 +141,11 @@ class F
/**
* Copy a file to a new location.
*
* @param string $source
* @param string $target
* @param bool $force
* @return bool
*/
public static function copy(string $source, string $target, bool $force = false): bool
{
@@ -173,6 +174,7 @@ class F
* </code>
*
* @param string $file The path
* @return string
*/
public static function dirname(string $file): string
{
@@ -181,13 +183,17 @@ class F
/**
* Checks if the file exists on disk
*
* @param string $file
* @param string $in
* @return bool
*/
public static function exists(string $file, string|null $in = null): bool
public static function exists(string $file, string $in = null): bool
{
try {
static::realpath($file, $in);
return true;
} catch (Exception) {
} catch (Exception $e) {
return false;
}
}
@@ -195,13 +201,12 @@ class F
/**
* Gets the extension of a file
*
* @param string|null $file The filename or path
* @param string|null $extension Set an optional extension to overwrite the current one
* @param string $file The filename or path
* @param string $extension Set an optional extension to overwrite the current one
* @return string
*/
public static function extension(
string|null $file = null,
string|null $extension = null
): string {
public static function extension(string $file = null, string $extension = null): string
{
// overwrite the current extension
if ($extension !== null) {
return static::name($file) . '.' . $extension;
@@ -213,16 +218,22 @@ class F
/**
* Converts a file extension to a mime type
*
* @param string $extension
* @return string|false
*/
public static function extensionToMime(string $extension): string|null
public static function extensionToMime(string $extension)
{
return Mime::fromExtension($extension);
}
/**
* Returns the file type for a passed extension
*
* @param string $extension
* @return string|false
*/
public static function extensionToType(string $extension): string|false
public static function extensionToType(string $extension)
{
foreach (static::$types as $type => $extensions) {
if (in_array($extension, $extensions) === true) {
@@ -235,8 +246,11 @@ class F
/**
* Returns all extensions for a certain file type
*
* @param string $type
* @return array
*/
public static function extensions(string|null $type = null): array
public static function extensions(string $type = null)
{
if ($type === null) {
return array_keys(Mime::types());
@@ -256,6 +270,7 @@ class F
* </code>
*
* @param string $name The path
* @return string
*/
public static function filename(string $name): string
{
@@ -266,17 +281,15 @@ class F
* Invalidate opcode cache for file.
*
* @param string $file The path of the file
* @return bool
*/
public static function invalidateOpcodeCache(string $file): bool
{
if (
function_exists('opcache_invalidate') &&
strlen(ini_get('opcache.restrict_api')) === 0
) {
if (function_exists('opcache_invalidate') && strlen(ini_get('opcache.restrict_api')) === 0) {
return opcache_invalidate($file, true);
} else {
return false;
}
return false;
}
/**
@@ -284,6 +297,7 @@ class F
*
* @param string $file Full path to the file
* @param string $value An extension or mime type
* @return bool
*/
public static function is(string $file, string $value): bool
{
@@ -302,6 +316,9 @@ class F
/**
* Checks if the file is readable
*
* @param string $file
* @return bool
*/
public static function isReadable(string $file): bool
{
@@ -310,6 +327,9 @@ class F
/**
* Checks if the file is writable
*
* @param string $file
* @return bool
*/
public static function isWritable(string $file): bool
{
@@ -322,6 +342,11 @@ class F
/**
* Create a (symbolic) link to a file
*
* @param string $source
* @param string $link
* @param string $method
* @return bool
*/
public static function link(string $source, string $link, string $method = 'link'): bool
{
@@ -337,7 +362,7 @@ class F
try {
return $method($source, $link) === true;
} catch (Throwable) {
} catch (Throwable $e) {
return false;
}
}
@@ -346,14 +371,13 @@ class F
* Loads a file and returns the result or `false` if the
* file to load does not exist
*
* @param string $file
* @param mixed $fallback
* @param array $data Optional array of variables to extract in the variable scope
* @return mixed
*/
public static function load(
string $file,
mixed $fallback = null,
array $data = [],
bool $allowOutput = true
) {
public static function load(string $file, $fallback = null, array $data = [])
{
if (is_file($file) === false) {
return $fallback;
}
@@ -361,21 +385,9 @@ class F
// we use the loadIsolated() method here to prevent the included
// file from overwriting our $fallback in this variable scope; see
// https://www.php.net/manual/en/function.include.php#example-124
$callback = fn () => static::loadIsolated($file, $data);
$result = static::loadIsolated($file, $data);
// if the loaded file should not produce any output,
// call the loaidIsolated method from the Response class
// which checks for unintended ouput and throws an error if detected
if ($allowOutput === false) {
$result = Response::guardAgainstOutput($callback);
} else {
$result = $callback();
}
if (
$fallback !== null &&
gettype($result) !== gettype($fallback)
) {
if ($fallback !== null && gettype($result) !== gettype($fallback)) {
return $fallback;
}
@@ -385,35 +397,37 @@ class F
/**
* A super simple class autoloader
* @since 3.7.0
*
* @param array $classmap
* @param string|null $base
* @return void
*/
public static function loadClasses(
array $classmap,
string|null $base = null
): void {
public static function loadClasses(array $classmap, ?string $base = null): void
{
// convert all classnames to lowercase
$classmap = array_change_key_case($classmap);
spl_autoload_register(
fn ($class) => Response::guardAgainstOutput(function () use ($class, $classmap, $base) {
$class = strtolower($class);
spl_autoload_register(function ($class) use ($classmap, $base) {
$class = strtolower($class);
if (isset($classmap[$class]) === false) {
return false;
}
if (!isset($classmap[$class])) {
return false;
}
if ($base) {
include $base . '/' . $classmap[$class];
} else {
include $classmap[$class];
}
})
);
if ($base) {
include $base . '/' . $classmap[$class];
} else {
include $classmap[$class];
}
});
}
/**
* Loads a file with as little as possible in the variable scope
*
* @param string $file
* @param array $data Optional array of variables to extract in the variable scope
* @return mixed
*/
protected static function loadIsolated(string $file, array $data = [])
{
@@ -426,48 +440,50 @@ class F
}
/**
* Loads a file using `include_once()` and
* returns whether loading was successful
* Loads a file using `include_once()` and returns whether loading was successful
*
* @param string $file
* @return bool
*/
public static function loadOnce(
string $file,
bool $allowOutput = true
): bool {
public static function loadOnce(string $file): bool
{
if (is_file($file) === false) {
return false;
}
$callback = fn () => include_once $file;
if ($allowOutput === false) {
Response::guardAgainstOutput($callback);
} else {
$callback();
}
include_once $file;
return true;
}
/**
* Returns the mime type of a file
*
* @param string $file
* @return string|false
*/
public static function mime(string $file): string|null
public static function mime(string $file)
{
return Mime::type($file);
}
/**
* Converts a mime type to a file extension
*
* @param string $mime
* @return string|false
*/
public static function mimeToExtension(string|null $mime = null): string|false
public static function mimeToExtension(string $mime = null)
{
return Mime::toExtension($mime);
}
/**
* Returns the type for a given mime
*
* @param string $mime
* @return string|false
*/
public static function mimeToType(string $mime): string|false
public static function mimeToType(string $mime)
{
return static::extensionToType(Mime::toExtension($mime));
}
@@ -475,14 +491,13 @@ class F
/**
* Get the file's last modification time.
*
* @param 'date'|'intl'|'strftime'|null $handler Custom date handler or `null`
* for the globally configured one
* @param string $file
* @param string|\IntlDateFormatter|null $format
* @param string $handler date, intl or strftime
* @return mixed
*/
public static function modified(
string $file,
string|IntlDateFormatter|null $format = null,
string|null $handler = null
): string|int|false {
public static function modified(string $file, $format = null, string $handler = 'date')
{
if (file_exists($file) !== true) {
return false;
}
@@ -498,6 +513,7 @@ class F
* @param string $oldRoot The current path for the file
* @param string $newRoot The path to the new location
* @param bool $force Force move if the target file exists
* @return bool
*/
public static function move(string $oldRoot, string $newRoot, bool $force = false): bool
{
@@ -515,39 +531,19 @@ class F
static::remove($newRoot);
}
$directory = dirname($newRoot);
// create the parent directory if it does not exist
if (is_dir($directory) === false) {
Dir::make($directory, true);
// actually move the file if it exists
if (rename($oldRoot, $newRoot) !== true) {
return false;
}
// atomically moving the file will only work if
// source and target are on the same filesystem
if (stat($oldRoot)['dev'] === stat($directory)['dev']) {
// same filesystem, we can move the file
return rename($oldRoot, $newRoot) === true;
}
// @codeCoverageIgnoreStart
// not the same filesystem; we need to copy
// the file and unlink the source afterwards
if (copy($oldRoot, $newRoot) === true) {
return unlink($oldRoot) === true;
}
// copying failed, ensure the new root isn't there
// (e.g. if the file could be created but there's no
// more remaining disk space to write its contents)
static::remove($newRoot);
return false;
// @codeCoverageIgnoreEnd
return true;
}
/**
* Extracts the name from a file path or filename without extension
*
* @param string $name The path or filename
* @return string
*/
public static function name(string $name): string
{
@@ -557,15 +553,14 @@ class F
/**
* Converts an integer size into a human readable format
*
* @param int|string|array $size The file size, a file path or array of paths
* @param string|false|null $locale Locale for number formatting,
* @param mixed $size The file size, a file path or array of paths
* @param string|null|false $locale Locale for number formatting,
* `null` for the current locale,
* `false` to disable number formatting
* @return string
*/
public static function niceSize(
int|string|array $size,
string|false|null $locale = null
): string {
public static function niceSize($size, $locale = null): string
{
// file mode
if (is_string($size) === true || is_array($size) === true) {
$size = static::size($size);
@@ -580,7 +575,7 @@ class F
}
// the math magic
$size = round($size / 1024 ** ($unit = floor(log($size, 1024))), 2);
$size = round($size / pow(1024, ($unit = floor(log($size, 1024)))), 2);
// format the number if requested
if ($locale !== false) {
@@ -595,27 +590,31 @@ class F
* contents of a remote HTTP or HTTPS URL
*
* @param string $file The path for the file or an absolute URL
* @return string|false
*/
public static function read(string $file): string|false
public static function read(string $file)
{
if (
is_readable($file) !== true &&
is_file($file) !== true &&
Str::startsWith($file, 'https://') !== true &&
Str::startsWith($file, 'http://') !== true
) {
return false;
}
return file_get_contents($file);
return @file_get_contents($file);
}
/**
* Changes the name of the file without
* touching the extension
*
* @param string $file
* @param string $newName
* @param bool $overwrite Force overwrite existing files
* @return string|false
*/
public static function rename(string $file, string $newName, bool $overwrite = false): string|false
public static function rename(string $file, string $newName, bool $overwrite = false)
{
// create the new name
$name = static::safeName(basename($newName));
@@ -637,8 +636,12 @@ class F
/**
* Returns the absolute path to the file if the file can be found.
*
* @param string $file
* @param string $in
* @return string|null
*/
public static function realpath(string $file, string|null $in = null): string
public static function realpath(string $file, string $in = null)
{
$realpath = realpath($file);
@@ -666,8 +669,12 @@ class F
* starting after $in
*
* @SuppressWarnings(PHPMD.CountInLoopExpression)
*
* @param string $file
* @param string $in
* @return string
*/
public static function relativepath(string $file, string|null $in = null): string
public static function relativepath(string $file, string $in = null): string
{
if (empty($in) === true) {
return basename($file);
@@ -708,6 +715,7 @@ class F
* </code>
*
* @param string $file The path for the file
* @return bool
*/
public static function remove(string $file): bool
{
@@ -728,8 +736,7 @@ class F
}
/**
* Sanitize a file's full name (filename and extension)
* to strip unwanted special characters
* Sanitize a filename to strip unwanted special characters
*
* <code>
*
@@ -739,54 +746,25 @@ class F
* </code>
*
* @param string $string The file name
* @return string
*/
public static function safeName(string $string): string
{
$basename = static::safeBasename($string);
$extension = static::safeExtension($string);
$name = static::name($string);
$extension = static::extension($string);
$safeName = Str::slug($name, '-', 'a-z0-9@._-');
$safeExtension = empty($extension) === false ? '.' . Str::slug($extension) : '';
if (empty($extension) === false) {
$extension = '.' . $extension;
}
return $basename . $extension;
}
/**
* Sanitize a file's name (without extension)
* @since 4.0.0
*/
public static function safeBasename(
string $string,
bool $extract = true
): string {
// extract only the name part from whole filename string
if ($extract === true) {
$string = static::name($string);
}
return Str::slug($string, '-', 'a-z0-9@._-');
}
/**
* Sanitize a file's extension
* @since 4.0.0
*/
public static function safeExtension(
string $string,
bool $extract = true
): string {
// extract only the extension part from whole filename string
if ($extract === true) {
$string = static::extension($string);
}
return Str::slug($string);
return $safeName . $safeExtension;
}
/**
* Tries to find similar or the same file by
* building a glob based on the path
*
* @param string $path
* @param string $pattern
* @return array
*/
public static function similar(string $path, string $pattern = '*'): array
{
@@ -801,8 +779,9 @@ class F
* Returns the size of a file or an array of files.
*
* @param string|array $file file path or array of paths
* @return int
*/
public static function size(string|array $file): int
public static function size($file): int
{
if (is_array($file) === true) {
return array_reduce(
@@ -812,19 +791,20 @@ class F
);
}
if ($size = @filesize($file)) {
return $size;
try {
return filesize($file);
} catch (Throwable $e) {
return 0;
}
return 0;
}
/**
* Categorize the file
*
* @param string $file Either the file path or extension
* @return string|null
*/
public static function type(string $file): string|null
public static function type(string $file)
{
$length = strlen($file);
@@ -857,8 +837,11 @@ class F
/**
* Returns all extensions of a given file type
* or `null` if the file type is unknown
*
* @param string $type
* @return array|null
*/
public static function typeToExtensions(string $type): array|null
public static function typeToExtensions(string $type): ?array
{
return static::$types[$type] ?? null;
}
@@ -871,15 +854,28 @@ class F
{
return Helpers::handleErrors(
fn (): bool => unlink($file),
// if the file or link was already deleted (race condition),
fn (int $errno, string $errstr): bool => Str::endsWith($errstr, 'No such file or directory'),
// consider it a success
true
function (&$override, int $errno, string $errstr): bool {
// if the file or link was already deleted (race condition),
// consider it a success
if (Str::endsWith($errstr, 'No such file or directory') === true) {
$override = true;
// drop the warning
return true;
}
// handle every other warning normally
return false;
}
);
}
/**
* Unzips a zip file
*
* @param string $file
* @param string $to
* @return bool
*/
public static function unzip(string $file, string $to): bool
{
@@ -902,8 +898,9 @@ class F
* Returns the file as data uri
*
* @param string $file The path for the file
* @return string|false
*/
public static function uri(string $file): string|false
public static function uri(string $file)
{
if ($mime = static::mime($file)) {
return 'data:' . $mime . ';base64,' . static::base64($file);
@@ -918,6 +915,7 @@ class F
* @param string $file The path for the new file
* @param mixed $content Either a string, an object or an array. Arrays and objects will be serialized.
* @param bool $append true: append the content to an existing file if available. false: overwrite.
* @return bool
*/
public static function write(string $file, $content, bool $append = false): bool
{

View File

@@ -2,14 +2,13 @@
namespace Kirby\Filesystem;
use IntlDateFormatter;
use Kirby\Cms\App;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Http\Response;
use Kirby\Sane\Sane;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\Html;
use Kirby\Toolkit\Properties;
use Kirby\Toolkit\V;
/**
@@ -26,26 +25,28 @@ use Kirby\Toolkit\V;
*/
class File
{
/**
* Parent file model
* The model object must use the `\Kirby\Filesystem\IsFile` trait
*/
protected object|null $model;
use Properties;
/**
* Absolute file path
*
* @var string
*/
protected string|null $root;
protected $root;
/**
* Absolute file URL
*
* @var string|null
*/
protected string|null $url;
protected $url;
/**
* Validation rules to be used for `::match()`
*
* @var array
*/
public static array $validations = [
public static $validations = [
'maxsize' => ['size', 'max'],
'minsize' => ['size', 'min']
];
@@ -55,15 +56,12 @@ class File
*
* @param array|string|null $props Properties or deprecated `$root` string
* @param string|null $url Deprecated argument, use `$props['url']` instead
*
* @throws \Kirby\Exception\InvalidArgumentException When the model does not use the `Kirby\Filesystem\IsFile` trait
*/
public function __construct(
array|string $props = null,
string $url = null
) {
public function __construct($props = null, string $url = null)
{
// Legacy support for old constructor of
// the `Kirby\Image\Image` class
// @todo 4.0.0 remove
if (is_array($props) === false) {
$props = [
'root' => $props,
@@ -71,21 +69,13 @@ class File
];
}
$this->root = $props['root'] ?? null;
$this->url = $props['url'] ?? null;
$this->model = $props['model'] ?? null;
if (
$this->model !== null &&
method_exists($this->model, 'hasIsFileTrait') !== true
) {
throw new InvalidArgumentException('The model object must use the "Kirby\Filesystem\IsFile" trait');
}
$this->setProperties($props);
}
/**
* Improved `var_dump` output
* @codeCoverageIgnore
*
* @return array
*/
public function __debugInfo(): array
{
@@ -94,6 +84,8 @@ class File
/**
* Returns the URL for the file object
*
* @return string
*/
public function __toString(): string
{
@@ -102,6 +94,8 @@ class File
/**
* Returns the file content as base64 encoded string
*
* @return string
*/
public function base64(): string
{
@@ -110,11 +104,15 @@ class File
/**
* Copy a file to a new location.
*
* @param string $target
* @param bool $force
* @return static
*/
public function copy(string $target, bool $force = false): static
public function copy(string $target, bool $force = false)
{
if (F::copy($this->root(), $target, $force) !== true) {
throw new Exception('The file "' . $this->root() . '" could not be copied');
if (F::copy($this->root, $target, $force) !== true) {
throw new Exception('The file "' . $this->root . '" could not be copied');
}
return new static($target);
@@ -124,6 +122,7 @@ class File
* Returns the file as data uri
*
* @param bool $base64 Whether the data should be base64 encoded or not
* @return string
*/
public function dataUri(bool $base64 = true): string
{
@@ -136,64 +135,77 @@ class File
/**
* Deletes the file
*
* @return bool
*/
public function delete(): bool
{
if (F::remove($this->root()) !== true) {
throw new Exception('The file "' . $this->root() . '" could not be deleted');
if (F::remove($this->root) !== true) {
throw new Exception('The file "' . $this->root . '" could not be deleted');
}
return true;
}
/*
* Automatically sends all needed headers
* for the file to be downloaded and
* echos the file's content
* Automatically sends all needed headers for the file to be downloaded
* and echos the file's content
*
* @param string|null $filename Optional filename for the download
* @return string
*/
public function download(string|null $filename = null): string
public function download($filename = null): string
{
return Response::download($this->root(), $filename ?? $this->filename());
return Response::download($this->root, $filename ?? $this->filename());
}
/**
* Checks if the file actually exists
*
* @return bool
*/
public function exists(): bool
{
return file_exists($this->root()) === true;
return file_exists($this->root) === true;
}
/**
* Returns the current lowercase extension (without .)
*
* @return string
*/
public function extension(): string
{
return F::extension($this->root());
return F::extension($this->root);
}
/**
* Returns the filename
*
* @return string
*/
public function filename(): string
{
return basename($this->root());
return basename($this->root);
}
/**
* Returns a md5 hash of the root
*
* @return string
*/
public function hash(): string
{
return md5($this->root());
return md5($this->root);
}
/**
* Sends an appropriate header for the asset
*
* @param bool $send
* @return \Kirby\Http\Response|void
*/
public function header(bool $send = true): Response|null
public function header(bool $send = true)
{
$response = new Response('', $this->mime());
@@ -202,11 +214,13 @@ class File
}
$response->send();
return null;
}
/**
* Converts the file to html
*
* @param array $attr
* @return string
*/
public function html(array $attr = []): string
{
@@ -217,22 +231,27 @@ class File
* Checks if a file is of a certain type
*
* @param string $value An extension or mime type
* @return bool
*/
public function is(string $value): bool
{
return F::is($this->root(), $value);
return F::is($this->root, $value);
}
/**
* Checks if the file is readable
*
* @return bool
*/
public function isReadable(): bool
{
return is_readable($this->root()) === true;
return is_readable($this->root) === true;
}
/**
* Checks if the file is a resizable image
*
* @return bool
*/
public function isResizable(): bool
{
@@ -242,6 +261,8 @@ class File
/**
* Checks if a preview can be displayed for the file
* in the panel or in the frontend
*
* @return bool
*/
public function isViewable(): bool
{
@@ -250,16 +271,20 @@ class File
/**
* Checks if the file is writable
*
* @return bool
*/
public function isWritable(): bool
{
return F::isWritable($this->root());
return F::isWritable($this->root);
}
/**
* Returns the app instance if it exists
*
* @return \Kirby\Cms\App|null
*/
public function kirby(): App|null
public function kirby()
{
return App::instance(null, true);
}
@@ -268,6 +293,8 @@ class File
* Runs a set of validations on the file object
* (mainly for images).
*
* @param array $rules
* @return bool
* @throws \Kirby\Exception\Exception
*/
public function match(array $rules): bool
@@ -277,15 +304,6 @@ class File
if (is_array($rules['mime'] ?? null) === true) {
$mime = $this->mime();
// the MIME type could not be determined, but matching
// to it was requested explicitly
if ($mime === null) {
throw new Exception([
'key' => 'file.mime.missing',
'data' => ['filename' => $this->filename()]
]);
}
// determine if any pattern matches the MIME type;
// once any pattern matches, `$carry` is `true` and the rest is skipped
$matches = array_reduce(
@@ -343,42 +361,43 @@ class File
/**
* Detects the mime type of the file
*
* @return string|null
*/
public function mime(): string|null
public function mime()
{
return Mime::type($this->root());
}
/**
* Returns the parent file model, which uses this instance as proxied file asset
*/
public function model(): object|null
{
return $this->model;
return Mime::type($this->root);
}
/**
* Returns the file's last modification time
*
* @param 'date'|'intl'|'strftime'|null $handler Custom date handler or `null`
* for the globally configured one
* @param string|\IntlDateFormatter|null $format
* @param string|null $handler date, intl or strftime
* @return mixed
*/
public function modified(
string|IntlDateFormatter|null $format = null,
string|null $handler = null
): string|int|false {
return F::modified($this->root(), $format, $handler);
public function modified($format = null, ?string $handler = null)
{
$kirby = $this->kirby();
return F::modified(
$this->root,
$format,
$handler ?? ($kirby ? $kirby->option('date.handler', 'date') : 'date')
);
}
/**
* Move the file to a new location
*
* @param string $newRoot
* @param bool $overwrite Force overwriting any existing files
* @return static
*/
public function move(string $newRoot, bool $overwrite = false): static
public function move(string $newRoot, bool $overwrite = false)
{
if (F::move($this->root(), $newRoot, $overwrite) !== true) {
throw new Exception('The file: "' . $this->root() . '" could not be moved to: "' . $newRoot . '"');
if (F::move($this->root, $newRoot, $overwrite) !== true) {
throw new Exception('The file: "' . $this->root . '" could not be moved to: "' . $newRoot . '"');
}
return new static($newRoot);
@@ -387,53 +406,62 @@ class File
/**
* Getter for the name of the file
* without the extension
*
* @return string
*/
public function name(): string
{
return pathinfo($this->root(), PATHINFO_FILENAME);
return pathinfo($this->root, PATHINFO_FILENAME);
}
/**
* Returns the file size in a
* human-readable format
*
* @param string|false|null $locale Locale for number formatting,
* @param string|null|false $locale Locale for number formatting,
* `null` for the current locale,
* `false` to disable number formatting
* @return string
*/
public function niceSize(string|false|null $locale = null): string
public function niceSize($locale = null): string
{
return F::niceSize($this->root(), $locale);
return F::niceSize($this->root, $locale);
}
/**
* Reads the file content and returns it.
*
* @return string|false
*/
public function read(): string|false
public function read()
{
return F::read($this->root());
return F::read($this->root);
}
/**
* Returns the absolute path to the file
*
* @return string
*/
public function realpath(): string
{
return realpath($this->root());
return realpath($this->root);
}
/**
* Changes the name of the file without
* touching the extension
*
* @param string $newName
* @param bool $overwrite Force overwrite existing files
* @return static
*/
public function rename(string $newName, bool $overwrite = false): static
public function rename(string $newName, bool $overwrite = false)
{
$newRoot = F::rename($this->root(), $newName, $overwrite);
$newRoot = F::rename($this->root, $newName, $overwrite);
if ($newRoot === false) {
throw new Exception('The file: "' . $this->root() . '" could not be renamed to: "' . $newName . '"');
throw new Exception('The file: "' . $this->root . '" could not be renamed to: "' . $newName . '"');
}
return new static($newRoot);
@@ -441,21 +469,46 @@ class File
/**
* Returns the given file path
*
* @return string|null
*/
public function root(): string|null
public function root(): ?string
{
return $this->root ??= $this->model?->root();
return $this->root;
}
/**
* Setter for the root
*
* @param string|null $root
* @return $this
*/
protected function setRoot(?string $root = null)
{
$this->root = $root;
return $this;
}
/**
* Setter for the file url
*
* @param string|null $url
* @return $this
*/
protected function setUrl(?string $url = null)
{
$this->url = $url;
return $this;
}
/**
* Returns the absolute url for the file
*
* @return string|null
*/
public function url(): string|null
public function url(): ?string
{
// lazily determine the URL from the model object
// only if it's needed to avoid breaking custom file::url
// components that rely on `$cmsFile->asset()` methods
return $this->url ??= $this->model?->url();
return $this->url;
}
/**
@@ -466,13 +519,14 @@ class File
* @param string|bool $typeLazy Explicit sane handler type string,
* `true` for lazy autodetection or
* `false` for normal autodetection
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\LogicException If more than one handler applies
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public function sanitizeContents(string|bool $typeLazy = false): void
public function sanitizeContents($typeLazy = false): void
{
Sane::sanitizeFile($this->root(), $typeLazy);
}
@@ -480,23 +534,29 @@ class File
/**
* Returns the sha1 hash of the file
* @since 3.6.0
*
* @return string
*/
public function sha1(): string
{
return sha1_file($this->root());
return sha1_file($this->root);
}
/**
* Returns the raw size of the file
*
* @return int
*/
public function size(): int
{
return F::size($this->root());
return F::size($this->root);
}
/**
* Converts the media object to a
* plain PHP array
*
* @return array
*/
public function toArray(): array
{
@@ -522,6 +582,8 @@ class File
/**
* Converts the entire file array into
* a json string
*
* @return string
*/
public function toJson(): string
{
@@ -530,10 +592,12 @@ class File
/**
* Returns the file type.
*
* @return string|null
*/
public function type(): string|null
public function type(): ?string
{
return F::type($this->root());
return F::type($this->root);
}
/**
@@ -542,23 +606,27 @@ class File
* @param string|bool $typeLazy Explicit sane handler type string,
* `true` for lazy autodetection or
* `false` for normal autodetection
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public function validateContents(string|bool $typeLazy = false): void
public function validateContents($typeLazy = false): void
{
Sane::validateFile($this->root(), $typeLazy);
}
/**
* Writes content to the file
*
* @param string $content
* @return bool
*/
public function write(string $content): bool
public function write($content): bool
{
if (F::write($this->root(), $content) !== true) {
throw new Exception('The file "' . $this->root() . '" could not be written');
if (F::write($this->root, $content) !== true) {
throw new Exception('The file "' . $this->root . '" could not be written');
}
return true;

View File

@@ -30,31 +30,45 @@ class Filename
{
/**
* List of all applicable attributes
*
* @var array
*/
protected array $attributes;
protected $attributes;
/**
* The sanitized file extension
*
* @var string
*/
protected string $extension;
protected $extension;
/**
* The source original filename
*
* @var string
*/
protected string $filename;
protected $filename;
/**
* The sanitized file name
*
* @var string
*/
protected string $name;
protected $name;
/**
* The template for the final name
*
* @var string
*/
protected string $template;
protected $template;
/**
* Creates a new Filename object
*
* @param string $filename
* @param string $template
* @param array $attributes
*/
public function __construct(string $filename, string $template, array $attributes = [])
{
@@ -65,11 +79,13 @@ class Filename
$attributes['format'] ??
pathinfo($filename, PATHINFO_EXTENSION)
);
$this->name = $this->sanitizeName($filename);
$this->name = $this->sanitizeName(pathinfo($filename, PATHINFO_FILENAME));
}
/**
* Converts the entire object to a string
*
* @return string
*/
public function __toString(): string
{
@@ -80,6 +96,8 @@ class Filename
* Converts all processed attributes
* to an array. The array keys are already
* the shortened versions for the filename
*
* @return array
*/
public function attributesToArray(): array
{
@@ -89,7 +107,6 @@ class Filename
'blur' => $this->blur(),
'bw' => $this->grayscale(),
'q' => $this->quality(),
'sharpen' => $this->sharpen(),
];
$array = array_filter(
@@ -106,8 +123,9 @@ class Filename
* new filename
*
* @param string|null $prefix The prefix will be used in the filename creation
* @return string
*/
public function attributesToString(string|null $prefix = null): string
public function attributesToString(string $prefix = null): string
{
$array = $this->attributesToArray();
$result = [];
@@ -117,11 +135,16 @@ class Filename
$value = '';
}
$result[] = match ($key) {
'dimensions' => $value,
'crop' => ($value === 'center') ? 'crop' : $key . '-' . $value,
default => $key . $value
};
switch ($key) {
case 'dimensions':
$result[] = $value;
break;
case 'crop':
$result[] = ($value === 'center') ? 'crop' : $key . '-' . $value;
break;
default:
$result[] = $key . $value;
}
}
$result = array_filter($result);
@@ -136,8 +159,10 @@ class Filename
/**
* Normalizes the blur option value
*
* @return false|int
*/
public function blur(): int|false
public function blur()
{
$value = $this->attributes['blur'] ?? false;
@@ -150,8 +175,10 @@ class Filename
/**
* Normalizes the crop option value
*
* @return false|string
*/
public function crop(): string|false
public function crop()
{
// get the crop value
$crop = $this->attributes['crop'] ?? false;
@@ -167,8 +194,10 @@ class Filename
* Returns a normalized array
* with width and height values
* if available
*
* @return array
*/
public function dimensions(): array
public function dimensions()
{
if (empty($this->attributes['width']) === true && empty($this->attributes['height']) === true) {
return [];
@@ -182,6 +211,8 @@ class Filename
/**
* Returns the sanitized extension
*
* @return string
*/
public function extension(): string
{
@@ -194,6 +225,8 @@ class Filename
* the option. You can use `grayscale`,
* `greyscale` or simply `bw`. The function
* will always return `grayscale`
*
* @return bool
*/
public function grayscale(): bool
{
@@ -206,6 +239,8 @@ class Filename
/**
* Returns the filename without extension
*
* @return string
*/
public function name(): string
{
@@ -214,8 +249,10 @@ class Filename
/**
* Normalizes the quality option value
*
* @return false|int
*/
public function quality(): int|false
public function quality()
{
$value = $this->attributes['quality'] ?? false;
@@ -228,37 +265,36 @@ class Filename
/**
* Sanitizes the file extension.
* It also replaces `jpeg` with `jpg`.
* The extension will be converted
* to lowercase and `jpeg` will be
* replaced with `jpg`
*
* @param string $extension
* @return string
*/
protected function sanitizeExtension(string $extension): string
{
$extension = F::safeExtension('test.' . $extension);
$extension = strtolower($extension);
$extension = str_replace('jpeg', 'jpg', $extension);
return $extension;
}
/**
* Sanitizes the file name
* Sanitizes the name with Kirby's
* Str::slug function
*
* @param string $name
* @return string
*/
protected function sanitizeName(string $name): string
{
return F::safeBasename($name);
}
/**
* Normalizes the sharpen option value
*/
public function sharpen(): int|false
{
return match ($this->attributes['sharpen'] ?? false) {
false => false,
true => 50,
default => (int)$this->attributes['sharpen']
};
return Str::slug($name);
}
/**
* Returns the converted filename as string
*
* @return string
*/
public function toString(): string
{

View File

@@ -5,6 +5,7 @@ namespace Kirby\Filesystem;
use Kirby\Cms\App;
use Kirby\Exception\BadMethodCallException;
use Kirby\Image\Image;
use Kirby\Toolkit\Properties;
/**
* Trait for all objects that represent an asset file.
@@ -21,36 +22,48 @@ use Kirby\Image\Image;
*/
trait IsFile
{
use Properties;
/**
* File asset object
*
* @var \Kirby\Filesystem\File
*/
protected File|null $asset = null;
protected $asset;
/**
* Absolute file path
*
* @var string|null
*/
protected string|null $root;
protected $root;
/**
* Absolute file URL
*
* @var string|null
*/
protected string|null $url;
protected $url;
/**
* Constructor sets all file properties
*
* @param array $props
*/
public function __construct(array $props)
{
$this->root = $props['root'] ?? null;
$this->url = $props['url'] ?? null;
$this->setProperties($props);
}
/**
* Magic caller for asset methods
*
* @param string $method
* @param array $arguments
* @return mixed
* @throws \Kirby\Exception\BadMethodCallException
*/
public function __call(string $method, array $arguments = []): mixed
public function __call(string $method, array $arguments = [])
{
// public property access
if (isset($this->$method) === true) {
@@ -67,6 +80,8 @@ trait IsFile
/**
* Converts the asset to a string
*
* @return string
*/
public function __toString(): string
{
@@ -75,29 +90,33 @@ trait IsFile
/**
* Returns the file asset object
*
* @param array|string|null $props
* @return \Kirby\Filesystem\File
*/
public function asset(array|string|null $props = null): File
public function asset($props = null)
{
if ($this->asset !== null) {
return $this->asset;
}
$props ??= [];
$props = $props ?? [
'root' => $this->root(),
'url' => $this->url()
];
if (is_string($props) === true) {
$props = ['root' => $props];
switch ($this->type()) {
case 'image':
return $this->asset = new Image($props);
default:
return $this->asset = new File($props);
}
$props['model'] ??= $this;
return $this->asset = match ($this->type()) {
'image' => new Image($props),
default => new File($props)
};
}
/**
* Checks if the file exists on disk
*
* @return bool
*/
public function exists(): bool
{
@@ -107,37 +126,57 @@ trait IsFile
return file_exists($this->root()) === true;
}
/**
* To check the existence of the IsFile trait
*
* @todo Switch to class constant in traits when min PHP version 8.2 required
* @codeCoverageIgnore
*/
protected function hasIsFileTrait(): bool
{
return true;
}
/**
* Returns the app instance
*
* @return \Kirby\Cms\App
*/
public function kirby(): App
public function kirby()
{
return App::instance();
}
/**
* Returns the given file path
*
* @return string|null
*/
public function root(): string|null
public function root(): ?string
{
return $this->root;
}
/**
* Returns the file type
* Setter for the root
*
* @param string|null $root
* @return $this
*/
public function type(): string|null
protected function setRoot(?string $root = null)
{
$this->root = $root;
return $this;
}
/**
* Setter for the file url
*
* @param string|null $url
* @return $this
*/
protected function setUrl(?string $url = null)
{
$this->url = $url;
return $this;
}
/**
* Returns the file type
*
* @return string|null
*/
public function type(): ?string
{
// Important to include this in the trait
// to avoid infinite loops when trying
@@ -147,8 +186,10 @@ trait IsFile
/**
* Returns the absolute url for the file
*
* @return string|null
*/
public function url(): string|null
public function url(): ?string
{
return $this->url;
}

View File

@@ -2,7 +2,6 @@
namespace Kirby\Filesystem;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use SimpleXMLElement;
@@ -31,7 +30,7 @@ class Mime
'aifc' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'avi' => 'video/x-msvideo',
'avif' => 'image/avif',
'avif' => 'image/avif',
'bmp' => 'image/bmp',
'css' => 'text/css',
'csv' => ['text/csv', 'text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream'],
@@ -79,7 +78,6 @@ class Mime
'php' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'php3' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'phps' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'pht' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'phtml' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'png' => 'image/png',
'ppt' => ['application/powerpoint', 'application/vnd.ms-powerpoint'],
@@ -120,22 +118,24 @@ class Mime
/**
* Fixes an invalid MIME type guess for the given file
*
* @param string $file
* @param string $mime
* @param string $extension
* @return string|null
*/
public static function fix(
string $file,
string|null $mime = null,
string|null $extension = null
): string|null {
public static function fix(string $file, string $mime = null, string $extension = null)
{
// fixing map
$map = [
'text/html' => [
'svg' => [Mime::class, 'fromSvg'],
'svg' => ['Kirby\Filesystem\Mime', 'fromSvg'],
],
'text/plain' => [
'css' => 'text/css',
'json' => 'application/json',
'mjs' => 'text/javascript',
'svg' => [Mime::class, 'fromSvg'],
'svg' => ['Kirby\Filesystem\Mime', 'fromSvg'],
],
'text/x-asm' => [
'css' => 'text/css'
@@ -166,8 +166,11 @@ class Mime
/**
* Guesses a MIME type by extension
*
* @param string $extension
* @return string|null
*/
public static function fromExtension(string $extension): string|null
public static function fromExtension(string $extension): ?string
{
$mime = static::$types[$extension] ?? null;
return is_array($mime) === true ? array_shift($mime) : $mime;
@@ -175,8 +178,11 @@ class Mime
/**
* Returns the MIME type of a file
*
* @param string $file
* @return string|false
*/
public static function fromFileInfo(string $file): string|false
public static function fromFileInfo(string $file)
{
if (function_exists('finfo_file') === true && file_exists($file) === true) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
@@ -190,13 +196,13 @@ class Mime
/**
* Returns the MIME type of a file
*
* @param string $file
* @return string|false
*/
public static function fromMimeContentType(string $file): string|false
public static function fromMimeContentType(string $file)
{
if (
function_exists('mime_content_type') === true &&
file_exists($file) === true
) {
if (function_exists('mime_content_type') === true && file_exists($file) === true) {
return mime_content_type($file);
}
@@ -205,8 +211,11 @@ class Mime
/**
* Tries to detect a valid SVG and returns the MIME type accordingly
*
* @param string $file
* @return string|false
*/
public static function fromSvg(string $file): string|false
public static function fromSvg(string $file)
{
if (file_exists($file) === true) {
libxml_use_internal_errors(true);
@@ -224,6 +233,10 @@ class Mime
/**
* Tests if a given MIME type is matched by an `Accept` header
* pattern; returns true if the MIME type is contained at all
*
* @param string $mime
* @param string $pattern
* @return bool
*/
public static function isAccepted(string $mime, string $pattern): bool
{
@@ -242,6 +255,10 @@ class Mime
* Tests if a MIME wildcard pattern from an `Accept` header
* matches a given type
* @since 3.3.0
*
* @param string $test
* @param string $wildcard
* @return bool
*/
public static function matches(string $test, string $wildcard): bool
{
@@ -250,8 +267,11 @@ class Mime
/**
* Returns the extension for a given MIME type
*
* @param string|null $mime
* @return string|false
*/
public static function toExtension(string $mime = null): string|false
public static function toExtension(string $mime = null)
{
foreach (static::$types as $key => $value) {
if (is_array($value) === true && in_array($mime, $value) === true) {
@@ -268,33 +288,22 @@ class Mime
/**
* Returns all available extensions for a given MIME type
*
* @param string|null $mime
* @return array
*/
public static function toExtensions(string $mime = null, bool $matchWildcards = false): array
public static function toExtensions(string $mime = null): array
{
$extensions = [];
$testMime = fn (string $v) => static::matches($v, $mime);
foreach (static::$types as $key => $value) {
if (is_array($value) === true) {
if ($matchWildcards === true) {
if (A::some($value, $testMime)) {
$extensions[] = $key;
}
} else {
if (in_array($mime, $value) === true) {
$extensions[] = $key;
}
}
} else {
if ($matchWildcards === true) {
if ($testMime($value) === true) {
$extensions[] = $key;
}
} else {
if ($value === $mime) {
$extensions[] = $key;
}
}
if (is_array($value) === true && in_array($mime, $value) === true) {
$extensions[] = $key;
continue;
}
if ($value === $mime) {
$extensions[] = $key;
}
}
@@ -303,11 +312,13 @@ class Mime
/**
* Returns the MIME type of a file
*
* @param string $file
* @param string $extension
* @return string|false
*/
public static function type(
string $file,
string|null $extension = null
): string|null {
public static function type(string $file, string $extension = null)
{
// use the standard finfo extension
$mime = static::fromFileInfo($file);
@@ -330,6 +341,8 @@ class Mime
/**
* Returns all detectable MIME types
*
* @return array
*/
public static function types(): array
{