Upgrade to 3.6.0
This commit is contained in:
@@ -374,6 +374,20 @@ class A
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple wrapper around array_map
|
||||
* with a sane argument order
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param array $array
|
||||
* @param callable $map
|
||||
* @return array
|
||||
*/
|
||||
public static function map(array $array, callable $map): array
|
||||
{
|
||||
return array_map($map, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an array item to a new index
|
||||
*
|
||||
@@ -418,7 +432,7 @@ class A
|
||||
*
|
||||
* $required = ['cat', 'elephant'];
|
||||
*
|
||||
* $missng = A::missing($array, $required);
|
||||
* $missing = A::missing($array, $required);
|
||||
* // missing: [
|
||||
* // 'elephant'
|
||||
* // ];
|
||||
@@ -599,7 +613,7 @@ class A
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether an array is associative or not
|
||||
* Checks whether an array is associative or not
|
||||
*
|
||||
* <code>
|
||||
* $array = ['a', 'b', 'c'];
|
||||
|
@@ -287,12 +287,7 @@ class Collection extends Iterator implements Countable
|
||||
}
|
||||
|
||||
// get the filter from the filters array
|
||||
$filter = static::$filters[$operator] ?? null;
|
||||
|
||||
// return an unfiltered list if the filter does not exist
|
||||
if ($filter === null) {
|
||||
return $this;
|
||||
}
|
||||
$filter = static::$filters[$operator];
|
||||
|
||||
if (is_array($filter) === true) {
|
||||
$collection = clone $this;
|
||||
|
@@ -5,6 +5,7 @@ namespace Kirby\Toolkit;
|
||||
use ArgumentCountError;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Filesystem\F;
|
||||
use TypeError;
|
||||
|
||||
/**
|
||||
@@ -262,6 +263,11 @@ class Component
|
||||
if (isset($options['mixins']) === true) {
|
||||
foreach ($options['mixins'] as $mixin) {
|
||||
if (isset(static::$mixins[$mixin]) === true) {
|
||||
if (is_string(static::$mixins[$mixin]) === true) {
|
||||
// resolve a path to a mixin on demand
|
||||
static::$mixins[$mixin] = include static::$mixins[$mixin];
|
||||
}
|
||||
|
||||
$options = array_replace_recursive(static::$mixins[$mixin], $options);
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Filesystem\F;
|
||||
use ReflectionFunction;
|
||||
|
||||
/**
|
||||
|
@@ -1,444 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* The `Dir` class provides methods
|
||||
* for dealing with directories on the
|
||||
* file system level, like creating,
|
||||
* listing, moving, copying or
|
||||
* evaluating directories etc.
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
class Dir
|
||||
{
|
||||
/**
|
||||
* Ignore when scanning directories
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $ignore = [
|
||||
'.',
|
||||
'..',
|
||||
'.DS_Store',
|
||||
'.gitignore',
|
||||
'.git',
|
||||
'.svn',
|
||||
'.htaccess',
|
||||
'Thumb.db',
|
||||
'@eaDir'
|
||||
];
|
||||
|
||||
/**
|
||||
* Copy the directory to a new destination
|
||||
*
|
||||
* @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 $ignore = []): bool
|
||||
{
|
||||
if (is_dir($dir) === false) {
|
||||
throw new Exception('The directory "' . $dir . '" does not exist');
|
||||
}
|
||||
|
||||
if (is_dir($target) === true) {
|
||||
throw new Exception('The target directory "' . $target . '" exists');
|
||||
}
|
||||
|
||||
if (static::make($target) !== true) {
|
||||
throw new Exception('The target directory "' . $target . '" could not be created');
|
||||
}
|
||||
|
||||
foreach (static::read($dir) as $name) {
|
||||
$root = $dir . '/' . $name;
|
||||
|
||||
if (in_array($root, $ignore) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($root) === true) {
|
||||
if ($recursive === true) {
|
||||
static::copy($root, $target . '/' . $name, true, $ignore);
|
||||
}
|
||||
} else {
|
||||
F::copy($root, $target . '/' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all subdirectories
|
||||
*
|
||||
* @param string $dir
|
||||
* @param array $ignore
|
||||
* @param bool $absolute
|
||||
* @return array
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the directory exists on disk
|
||||
*
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
*/
|
||||
public static function exists(string $dir): bool
|
||||
{
|
||||
return is_dir($dir) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all files
|
||||
*
|
||||
* @param string $dir
|
||||
* @param array $ignore
|
||||
* @param bool $absolute
|
||||
* @return array
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the directory and all subdirectories
|
||||
*
|
||||
* @param string $dir
|
||||
* @param bool $recursive
|
||||
* @param array $ignore
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public static function index(string $dir, bool $recursive = false, array $ignore = null, string $path = null)
|
||||
{
|
||||
$result = [];
|
||||
$dir = realpath($dir);
|
||||
$items = static::read($dir);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$root = $dir . '/' . $item;
|
||||
$entry = $path !== null ? $path . '/' . $item: $item;
|
||||
$result[] = $entry;
|
||||
|
||||
if ($recursive === true && is_dir($root) === true) {
|
||||
$result = array_merge($result, static::index($root, true, $ignore, $entry));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the folder has any contents
|
||||
*
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEmpty(string $dir): bool
|
||||
{
|
||||
return count(static::read($dir)) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the directory is readable
|
||||
*
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
*/
|
||||
public static function isReadable(string $dir): bool
|
||||
{
|
||||
return is_readable($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the directory is writable
|
||||
*
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
*/
|
||||
public static function isWritable(string $dir): bool
|
||||
{
|
||||
return is_writable($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a (symbolic) link to a directory
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $link
|
||||
* @return bool
|
||||
*/
|
||||
public static function link(string $source, string $link): bool
|
||||
{
|
||||
Dir::make(dirname($link), true);
|
||||
|
||||
if (is_dir($link) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_dir($source) === false) {
|
||||
throw new Exception(sprintf('The directory "%s" does not exist and cannot be linked', $source));
|
||||
}
|
||||
|
||||
try {
|
||||
return symlink($source, $link) === true;
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new directory
|
||||
*
|
||||
* @param string $dir The path for the new directory
|
||||
* @param bool $recursive Create all parent directories, which don't exist
|
||||
* @return bool True: the dir has been created, false: creating failed
|
||||
* @throws \Exception If a file with the provided path already exists or the parent directory is not writable
|
||||
*/
|
||||
public static function make(string $dir, bool $recursive = true): bool
|
||||
{
|
||||
if (empty($dir) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_dir($dir) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_file($dir) === true) {
|
||||
throw new Exception(sprintf('A file with the name "%s" already exists', $dir));
|
||||
}
|
||||
|
||||
$parent = dirname($dir);
|
||||
|
||||
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 mkdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively check when the dir and all
|
||||
* subfolders have been modified for the last time.
|
||||
*
|
||||
* @param string $dir The path of the directory
|
||||
* @param string $format
|
||||
* @param string $handler
|
||||
* @return 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) {
|
||||
if (is_file($dir . '/' . $item) === true) {
|
||||
$newModified = filemtime($dir . '/' . $item);
|
||||
} else {
|
||||
$newModified = static::modified($dir . '/' . $item);
|
||||
}
|
||||
|
||||
$modified = ($newModified > $modified) ? $newModified : $modified;
|
||||
}
|
||||
|
||||
return $format !== null ? $handler($format, $modified) : $modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a directory to a new location
|
||||
*
|
||||
* @param string $old The current path of the directory
|
||||
* @param string $new The desired path where the dir should be moved to
|
||||
* @return bool true: the directory has been moved, false: moving failed
|
||||
*/
|
||||
public static function move(string $old, string $new): bool
|
||||
{
|
||||
if ($old === $new) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_dir($old) === false || is_dir($new) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static::make(dirname($new), true) !== true) {
|
||||
throw new Exception('The parent directory cannot be created');
|
||||
}
|
||||
|
||||
return rename($old, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a nicely formatted size of all the contents of the folder
|
||||
*
|
||||
* @param string $dir The path of the directory
|
||||
* @return mixed
|
||||
*/
|
||||
public static function niceSize(string $dir)
|
||||
{
|
||||
return F::niceSize(static::size($dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all files from a directory and returns them as an array.
|
||||
* It skips unwanted invisible stuff.
|
||||
*
|
||||
* @param string $dir The path of directory
|
||||
* @param array $ignore Optional array with filenames, which should be ignored
|
||||
* @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 $ignore = null, bool $absolute = false): array
|
||||
{
|
||||
if (is_dir($dir) === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// create the ignore pattern
|
||||
$ignore = $ignore ?? static::$ignore;
|
||||
$ignore = array_merge($ignore, ['.', '..']);
|
||||
|
||||
// scan for all files and dirs
|
||||
$result = array_values((array)array_diff(scandir($dir), $ignore));
|
||||
|
||||
// add absolute paths
|
||||
if ($absolute === true) {
|
||||
$result = array_map(function ($item) use ($dir) {
|
||||
return $dir . '/' . $item;
|
||||
}, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a folder including all containing files and folders
|
||||
*
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
*/
|
||||
public static function remove(string $dir): bool
|
||||
{
|
||||
$dir = realpath($dir);
|
||||
|
||||
if (is_dir($dir) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_link($dir) === true) {
|
||||
return unlink($dir);
|
||||
}
|
||||
|
||||
foreach (scandir($dir) as $childName) {
|
||||
if (in_array($childName, ['.', '..']) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$child = $dir . '/' . $childName;
|
||||
|
||||
if (is_link($child) === true) {
|
||||
unlink($child);
|
||||
} elseif (is_dir($child) === true) {
|
||||
static::remove($child);
|
||||
} else {
|
||||
F::remove($child);
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the directory and all subfolders and files
|
||||
*
|
||||
* @param string $dir The path of the directory
|
||||
* @return mixed
|
||||
*/
|
||||
public static function size(string $dir)
|
||||
{
|
||||
if (is_dir($dir) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$size = 0;
|
||||
$items = static::read($dir);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$root = $dir . '/' . $item;
|
||||
|
||||
if (is_dir($root) === true) {
|
||||
$size += static::size($root);
|
||||
} elseif (is_file($root) === true) {
|
||||
$size += F::size($root);
|
||||
}
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if (filemtime($dir) > $time) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$content = static::read($dir);
|
||||
|
||||
foreach ($content as $item) {
|
||||
$subdir = $dir . '/' . $item;
|
||||
|
||||
if (filemtime($subdir) > $time) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_dir($subdir) === true && static::wasModifiedAfter($subdir, $time) === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,865 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* The `F` class provides methods for
|
||||
* dealing with files on the file system
|
||||
* level, like creating, reading,
|
||||
* deleting, copying or validatings files.
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
class F
|
||||
{
|
||||
public static $types = [
|
||||
'archive' => [
|
||||
'gz',
|
||||
'gzip',
|
||||
'tar',
|
||||
'tgz',
|
||||
'zip',
|
||||
],
|
||||
'audio' => [
|
||||
'aif',
|
||||
'aiff',
|
||||
'm4a',
|
||||
'midi',
|
||||
'mp3',
|
||||
'wav',
|
||||
],
|
||||
'code' => [
|
||||
'css',
|
||||
'js',
|
||||
'json',
|
||||
'java',
|
||||
'htm',
|
||||
'html',
|
||||
'php',
|
||||
'rb',
|
||||
'py',
|
||||
'scss',
|
||||
'xml',
|
||||
'yaml',
|
||||
'yml',
|
||||
],
|
||||
'document' => [
|
||||
'csv',
|
||||
'doc',
|
||||
'docx',
|
||||
'dotx',
|
||||
'indd',
|
||||
'md',
|
||||
'mdown',
|
||||
'pdf',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'rtf',
|
||||
'txt',
|
||||
'xl',
|
||||
'xls',
|
||||
'xlsx',
|
||||
'xltx',
|
||||
],
|
||||
'image' => [
|
||||
'ai',
|
||||
'avif',
|
||||
'bmp',
|
||||
'gif',
|
||||
'eps',
|
||||
'ico',
|
||||
'j2k',
|
||||
'jp2',
|
||||
'jpeg',
|
||||
'jpg',
|
||||
'jpe',
|
||||
'png',
|
||||
'ps',
|
||||
'psd',
|
||||
'svg',
|
||||
'tif',
|
||||
'tiff',
|
||||
'webp'
|
||||
],
|
||||
'video' => [
|
||||
'avi',
|
||||
'flv',
|
||||
'm4v',
|
||||
'mov',
|
||||
'movie',
|
||||
'mpe',
|
||||
'mpg',
|
||||
'mp4',
|
||||
'ogg',
|
||||
'ogv',
|
||||
'swf',
|
||||
'webm',
|
||||
],
|
||||
];
|
||||
|
||||
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
|
||||
{
|
||||
return static::write($file, $content, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return base64_encode(static::read($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if (file_exists($source) === false || (file_exists($target) === true && $force === false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$directory = dirname($target);
|
||||
|
||||
// create the parent directory if it does not exist
|
||||
if (is_dir($directory) === false) {
|
||||
Dir::make($directory, true);
|
||||
}
|
||||
|
||||
return copy($source, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just an alternative for dirname() to stay consistent
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
* $dirname = F::dirname('/var/www/test.txt');
|
||||
* // dirname is /var/www
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
* @param string $file The path
|
||||
* @return string
|
||||
*/
|
||||
public static function dirname(string $file): string
|
||||
{
|
||||
return dirname($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file exists on disk
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $in
|
||||
* @return bool
|
||||
*/
|
||||
public static function exists(string $file, string $in = null): bool
|
||||
{
|
||||
try {
|
||||
static::realpath($file, $in);
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file
|
||||
*
|
||||
* @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 $file = null, string $extension = null): string
|
||||
{
|
||||
// overwrite the current extension
|
||||
if ($extension !== null) {
|
||||
return static::name($file) . '.' . $extension;
|
||||
}
|
||||
|
||||
// return the current extension
|
||||
return Str::lower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a file extension to a mime type
|
||||
*
|
||||
* @param string $extension
|
||||
* @return string|false
|
||||
*/
|
||||
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)
|
||||
{
|
||||
foreach (static::$types as $type => $extensions) {
|
||||
if (in_array($extension, $extensions) === true) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all extensions for a certain file type
|
||||
*
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public static function extensions(string $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
return array_keys(Mime::types());
|
||||
}
|
||||
|
||||
return static::$types[$type] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the filename from a file path
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
* $filename = F::filename('/var/www/test.txt');
|
||||
* // filename is test.txt
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
* @param string $name The path
|
||||
* @return string
|
||||
*/
|
||||
public static function filename(string $name): string
|
||||
{
|
||||
return pathinfo($name, PATHINFO_BASENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return opcache_invalidate($file, true);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file is of a certain type
|
||||
*
|
||||
* @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
|
||||
{
|
||||
// check for the extension
|
||||
if (in_array($value, static::extensions()) === true) {
|
||||
return static::extension($file) === $value;
|
||||
}
|
||||
|
||||
// check for the mime type
|
||||
if (strpos($value, '/') !== false) {
|
||||
return static::mime($file) === $value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file is readable
|
||||
*
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
public static function isReadable(string $file): bool
|
||||
{
|
||||
return is_readable($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file is writable
|
||||
*
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
public static function isWritable(string $file): bool
|
||||
{
|
||||
if (file_exists($file) === false) {
|
||||
return is_writable(dirname($file));
|
||||
}
|
||||
|
||||
return is_writable($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
Dir::make(dirname($link), true);
|
||||
|
||||
if (is_file($link) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_file($source) === false) {
|
||||
throw new Exception(sprintf('The file "%s" does not exist and cannot be linked', $source));
|
||||
}
|
||||
|
||||
try {
|
||||
return $method($source, $link) === true;
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, $fallback = null, array $data = [])
|
||||
{
|
||||
if (is_file($file) === false) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
// 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
|
||||
$result = static::loadIsolated($file, $data);
|
||||
|
||||
if ($fallback !== null && gettype($result) !== gettype($fallback)) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [])
|
||||
{
|
||||
// extract the $data variables in this scope to be accessed by the included file;
|
||||
// protect $file against being overwritten by a $data variable
|
||||
$___file___ = $file;
|
||||
extract($data);
|
||||
|
||||
return include $___file___;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file using `include_once()` and returns whether loading was successful
|
||||
*
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
public static function loadOnce(string $file): bool
|
||||
{
|
||||
if (is_file($file) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
include_once $file;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime type of a file
|
||||
*
|
||||
* @param string $file
|
||||
* @return string|false
|
||||
*/
|
||||
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 $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)
|
||||
{
|
||||
return static::extensionToType(Mime::toExtension($mime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's last modification time.
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $format
|
||||
* @param string $handler date or strftime
|
||||
* @return mixed
|
||||
*/
|
||||
public static function modified(string $file, string $format = null, string $handler = 'date')
|
||||
{
|
||||
if (file_exists($file) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stat = stat($file);
|
||||
$mtime = $stat['mtime'] ?? 0;
|
||||
$ctime = $stat['ctime'] ?? 0;
|
||||
$modified = max([$mtime, $ctime]);
|
||||
|
||||
if (is_null($format) === true) {
|
||||
return $modified;
|
||||
}
|
||||
|
||||
return $handler($format, $modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a file to a new location
|
||||
*
|
||||
* @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
|
||||
{
|
||||
// check if the file exists
|
||||
if (file_exists($oldRoot) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file_exists($newRoot) === true) {
|
||||
if ($force === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// delete the existing file
|
||||
static::remove($newRoot);
|
||||
}
|
||||
|
||||
// actually move the file if it exists
|
||||
if (rename($oldRoot, $newRoot) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return pathinfo($name, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an integer size into a human readable format
|
||||
*
|
||||
* @param mixed $size The file size or a file path
|
||||
* @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($size, $locale = null): string
|
||||
{
|
||||
// file mode
|
||||
if (is_string($size) === true && file_exists($size) === true) {
|
||||
$size = static::size($size);
|
||||
}
|
||||
|
||||
// make sure it's an int
|
||||
$size = (int)$size;
|
||||
|
||||
// avoid errors for invalid sizes
|
||||
if ($size <= 0) {
|
||||
return '0 KB';
|
||||
}
|
||||
|
||||
// the math magic
|
||||
$size = round($size / pow(1024, ($unit = floor(log($size, 1024)))), 2);
|
||||
|
||||
// format the number if requested
|
||||
if ($locale !== false) {
|
||||
$size = I18n::formatNumber($size, $locale);
|
||||
}
|
||||
|
||||
return $size . ' ' . static::$units[$unit];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content of a file or requests the
|
||||
* 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)
|
||||
{
|
||||
if (
|
||||
is_file($file) !== true &&
|
||||
Str::startsWith($file, 'https://') !== true &&
|
||||
Str::startsWith($file, 'http://') !== true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// create the new name
|
||||
$name = static::safeName(basename($newName));
|
||||
|
||||
// overwrite the root
|
||||
$newRoot = rtrim(dirname($file) . '/' . $name . '.' . F::extension($file), '.');
|
||||
|
||||
// nothing has changed
|
||||
if ($newRoot === $file) {
|
||||
return $newRoot;
|
||||
}
|
||||
|
||||
if (F::move($file, $newRoot, $overwrite) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $newRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $in = null)
|
||||
{
|
||||
$realpath = realpath($file);
|
||||
|
||||
if ($realpath === false || is_file($realpath) === false) {
|
||||
throw new Exception(sprintf('The file does not exist at the given path: "%s"', $file));
|
||||
}
|
||||
|
||||
if ($in !== null) {
|
||||
$parent = realpath($in);
|
||||
|
||||
if ($parent === false || is_dir($parent) === false) {
|
||||
throw new Exception(sprintf('The parent directory does not exist: "%s"', $in));
|
||||
}
|
||||
|
||||
if (substr($realpath, 0, strlen($parent)) !== $parent) {
|
||||
throw new Exception('The file is not within the parent directory');
|
||||
}
|
||||
}
|
||||
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative path of the file
|
||||
* starting after $in
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $in
|
||||
* @return string
|
||||
*/
|
||||
public static function relativepath(string $file, string $in = null): string
|
||||
{
|
||||
if (empty($in) === true) {
|
||||
return basename($file);
|
||||
}
|
||||
|
||||
// windows
|
||||
$file = str_replace('\\', '/', $file);
|
||||
$in = str_replace('\\', '/', $in);
|
||||
|
||||
if (Str::contains($file, $in) === false) {
|
||||
return basename($file);
|
||||
}
|
||||
|
||||
return Str::after($file, $in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a file
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
* $remove = F::remove('test.txt');
|
||||
* if($remove) echo 'The file has been removed';
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
* @param string $file The path for the file
|
||||
* @return bool
|
||||
*/
|
||||
public static function remove(string $file): bool
|
||||
{
|
||||
if (strpos($file, '*') !== false) {
|
||||
foreach (glob($file) as $f) {
|
||||
static::remove($f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$file = realpath($file);
|
||||
|
||||
if (file_exists($file) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return unlink($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a filename to strip unwanted special characters
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
* $safe = f::safeName('über genious.txt');
|
||||
* // safe will be ueber-genious.txt
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
* @param string $string The file name
|
||||
* @return string
|
||||
*/
|
||||
public static function safeName(string $string): string
|
||||
{
|
||||
$name = static::name($string);
|
||||
$extension = static::extension($string);
|
||||
$safeName = Str::slug($name, '-', 'a-z0-9@._-');
|
||||
$safeExtension = empty($extension) === false ? '.' . Str::slug($extension) : '';
|
||||
|
||||
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
|
||||
{
|
||||
$dir = dirname($path);
|
||||
$name = static::name($path);
|
||||
$extension = static::extension($path);
|
||||
$glob = $dir . '/' . $name . $pattern . '.' . $extension;
|
||||
return glob($glob);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of a file.
|
||||
*
|
||||
* @param mixed $file The path
|
||||
* @return int
|
||||
*/
|
||||
public static function size(string $file): int
|
||||
{
|
||||
try {
|
||||
return filesize($file);
|
||||
} catch (Throwable $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize the file
|
||||
*
|
||||
* @param string $file Either the file path or extension
|
||||
* @return string|null
|
||||
*/
|
||||
public static function type(string $file)
|
||||
{
|
||||
$length = strlen($file);
|
||||
|
||||
if ($length >= 2 && $length <= 4) {
|
||||
// use the file name as extension
|
||||
$extension = $file;
|
||||
} else {
|
||||
// get the extension from the filename
|
||||
$extension = pathinfo($file, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
if (empty($extension) === true) {
|
||||
// detect the mime type first to get the most reliable extension
|
||||
$mime = static::mime($file);
|
||||
$extension = static::mimeToExtension($mime);
|
||||
}
|
||||
|
||||
// sanitize extension
|
||||
$extension = strtolower($extension);
|
||||
|
||||
foreach (static::$types as $type => $extensions) {
|
||||
if (in_array($extension, $extensions) === true) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return static::$types[$type] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzips a zip file
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $to
|
||||
* @return bool
|
||||
*/
|
||||
public static function unzip(string $file, string $to): bool
|
||||
{
|
||||
if (class_exists('ZipArchive') === false) {
|
||||
throw new Exception('The ZipArchive class is not available');
|
||||
}
|
||||
|
||||
$zip = new ZipArchive();
|
||||
|
||||
if ($zip->open($file) === true) {
|
||||
$zip->extractTo($to);
|
||||
$zip->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file as data uri
|
||||
*
|
||||
* @param string $file The path for the file
|
||||
* @return string|false
|
||||
*/
|
||||
public static function uri(string $file)
|
||||
{
|
||||
if ($mime = static::mime($file)) {
|
||||
return 'data:' . $mime . ';base64,' . static::base64($file);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file
|
||||
*
|
||||
* @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 exisiting file if available. false: overwrite.
|
||||
* @return bool
|
||||
*/
|
||||
public static function write(string $file, $content, bool $append = false): bool
|
||||
{
|
||||
if (is_array($content) === true || is_object($content) === true) {
|
||||
$content = serialize($content);
|
||||
}
|
||||
|
||||
$mode = $append === true ? FILE_APPEND | LOCK_EX : LOCK_EX;
|
||||
|
||||
// if the parent directory does not exist, create it
|
||||
if (is_dir(dirname($file)) === false) {
|
||||
if (Dir::make(dirname($file)) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (static::isWritable($file) === false) {
|
||||
throw new Exception('The file "' . $file . '" is not writable');
|
||||
}
|
||||
|
||||
return file_put_contents($file, $content, $mode) !== false;
|
||||
}
|
||||
}
|
@@ -1,358 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Sane\Sane;
|
||||
|
||||
/**
|
||||
* Flexible File object with a set of helpful
|
||||
* methods to inspect and work with files.
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
class File
|
||||
{
|
||||
/**
|
||||
* Absolute file path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Constructs a new File object by absolute path
|
||||
*
|
||||
* @param string $root Absolute file path
|
||||
*/
|
||||
public function __construct(string $root = null)
|
||||
{
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file content as base64 encoded string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function base64(): string
|
||||
{
|
||||
return base64_encode($this->read());
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file to a new location.
|
||||
*
|
||||
* @param string $target
|
||||
* @param bool $force
|
||||
* @return 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');
|
||||
}
|
||||
|
||||
return new static($target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if ($base64 === true) {
|
||||
return 'data:' . $this->mime() . ';base64,' . $this->base64();
|
||||
}
|
||||
|
||||
return 'data:' . $this->mime() . ',' . Escape::url($this->read());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file actually exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return file_exists($this->root) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current lowercase extension (without .)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function extension(): string
|
||||
{
|
||||
return F::extension($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filename(): string
|
||||
{
|
||||
return basename($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a md5 hash of the root
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function hash(): string
|
||||
{
|
||||
return md5($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file is readable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return is_readable($this->root) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file is writable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return F::isWritable($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the mime type of the file
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function mime()
|
||||
{
|
||||
return Mime::type($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's last modification time.
|
||||
*
|
||||
* @param string $format
|
||||
* @param string $handler date or strftime
|
||||
* @return mixed
|
||||
*/
|
||||
public function modified(string $format = null, string $handler = 'date')
|
||||
{
|
||||
return F::modified($this->root, $format, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the name of the file
|
||||
* without the extension
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return pathinfo($this->root, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file size in a
|
||||
* human-readable format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function niceSize(): string
|
||||
{
|
||||
return F::niceSize($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the file content and returns it.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function read()
|
||||
{
|
||||
return F::read($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function realpath(): string
|
||||
{
|
||||
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)
|
||||
{
|
||||
$newRoot = F::rename($this->root, $newName, $overwrite);
|
||||
|
||||
if ($newRoot === false) {
|
||||
throw new Exception('The file: "' . $this->root . '" could not be renamed to: "' . $newName . '"');
|
||||
}
|
||||
|
||||
return new static($newRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given file path
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function root(): ?string
|
||||
{
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw size of the file
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function size(): int
|
||||
{
|
||||
return F::size($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the media object to a
|
||||
* plain PHP array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'root' => $this->root(),
|
||||
'hash' => $this->hash(),
|
||||
'filename' => $this->filename(),
|
||||
'name' => $this->name(),
|
||||
'safeName' => F::safeName($this->name()),
|
||||
'extension' => $this->extension(),
|
||||
'size' => $this->size(),
|
||||
'niceSize' => $this->niceSize(),
|
||||
'modified' => $this->modified('c'),
|
||||
'mime' => $this->mime(),
|
||||
'type' => $this->type(),
|
||||
'isWritable' => $this->isWritable(),
|
||||
'isReadable' => $this->isReadable(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file type.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return F::type($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the file contents depending on the file type
|
||||
*
|
||||
* @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($typeLazy = false): void
|
||||
{
|
||||
Sane::validateFile($this->root(), $typeLazy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes content to the file
|
||||
*
|
||||
* @param string $content
|
||||
* @return bool
|
||||
*/
|
||||
public function write($content): bool
|
||||
{
|
||||
if (F::write($this->root, $content) !== true) {
|
||||
throw new Exception('The file "' . $this->root . '" could not be written');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Http\Url;
|
||||
|
||||
/**
|
||||
@@ -194,7 +195,7 @@ class Html extends Xml
|
||||
return str_replace($search, $values, $string);
|
||||
}
|
||||
|
||||
return htmlentities($string, ENT_COMPAT, 'utf-8');
|
||||
return htmlentities($string, ENT_QUOTES, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,7 +306,7 @@ class Html extends Xml
|
||||
$text = $attr['href'];
|
||||
}
|
||||
|
||||
if (is_string($text) === true && Str::isUrl($text) === true) {
|
||||
if (is_string($text) === true && V::url($text) === true) {
|
||||
$text = Url::short($text);
|
||||
}
|
||||
|
||||
@@ -355,7 +356,7 @@ class Html extends Xml
|
||||
if ($content === null) {
|
||||
$content = '';
|
||||
}
|
||||
|
||||
|
||||
// force void elements to be self-closing
|
||||
if (static::isVoid($name) === true) {
|
||||
$content = null;
|
||||
@@ -419,21 +420,62 @@ class Html extends Xml
|
||||
* @param array $options Additional `vimeo` and `youtube` options
|
||||
* (will be used as query params in the embed URL)
|
||||
* @param array $attr Additional attributes for the `<iframe>` tag
|
||||
* @return string The generated HTML
|
||||
* @return string|null The generated HTML
|
||||
*/
|
||||
public static function video(string $url, array $options = [], array $attr = []): string
|
||||
public static function video(string $url, array $options = [], array $attr = []): ?string
|
||||
{
|
||||
// YouTube video
|
||||
if (preg_match('!youtu!i', $url) === 1) {
|
||||
if (Str::contains($url, 'youtu', true) === true) {
|
||||
return static::youtube($url, $options['youtube'] ?? [], $attr);
|
||||
}
|
||||
|
||||
// Vimeo video
|
||||
if (preg_match('!vimeo!i', $url) === 1) {
|
||||
if (Str::contains($url, 'vimeo', true) === true) {
|
||||
return static::vimeo($url, $options['vimeo'] ?? [], $attr);
|
||||
}
|
||||
|
||||
throw new Exception('Unexpected video type');
|
||||
// self-hosted video file
|
||||
$extension = F::extension($url);
|
||||
$type = F::extensionToType($extension);
|
||||
$mime = F::extensionToMime($extension);
|
||||
|
||||
// ignore unknown file types
|
||||
if ($type !== 'video') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::tag('video', [
|
||||
static::tag('source', null, [
|
||||
'src' => $url,
|
||||
'type' => $mime
|
||||
])
|
||||
], $attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of attributes
|
||||
* for video iframes
|
||||
*
|
||||
* @param array $attr
|
||||
* @return array
|
||||
*/
|
||||
public static function videoAttr(array $attr = []): array
|
||||
{
|
||||
// allow fullscreen mode by default
|
||||
// and use new `allow` attribute
|
||||
if (
|
||||
isset($attr['allow']) === false &&
|
||||
($attr['allowfullscreen'] ?? true) === true
|
||||
) {
|
||||
$attr['allow'] = 'fullscreen';
|
||||
}
|
||||
|
||||
// remove deprecated attribute
|
||||
if (isset($attr['allowfullscreen']) === true) {
|
||||
unset($attr['allowfullscreen']);
|
||||
}
|
||||
|
||||
return $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,28 +484,38 @@ class Html extends Xml
|
||||
* @param string $url Vimeo video URL
|
||||
* @param array $options Query params for the embed URL
|
||||
* @param array $attr Additional attributes for the `<iframe>` tag
|
||||
* @return string The generated HTML
|
||||
* @return string|null The generated HTML
|
||||
*/
|
||||
public static function vimeo(string $url, array $options = [], array $attr = []): string
|
||||
public static function vimeo(string $url, array $options = [], array $attr = []): ?string
|
||||
{
|
||||
if (preg_match('!vimeo.com\/([0-9]+)!i', $url, $array) === 1) {
|
||||
$id = $array[1];
|
||||
} elseif (preg_match('!player.vimeo.com\/video\/([0-9]+)!i', $url, $array) === 1) {
|
||||
$id = $array[1];
|
||||
} else {
|
||||
throw new Exception('Invalid Vimeo source');
|
||||
$uri = new Uri($url);
|
||||
$path = $uri->path();
|
||||
$query = $uri->query();
|
||||
$id = null;
|
||||
|
||||
switch ($uri->host()) {
|
||||
case 'vimeo.com':
|
||||
case 'www.vimeo.com':
|
||||
$id = $path->first();
|
||||
break;
|
||||
case 'player.vimeo.com':
|
||||
$id = $path->nth(1);
|
||||
break;
|
||||
}
|
||||
|
||||
// build the options query
|
||||
if (empty($options) === false) {
|
||||
$query = '?' . http_build_query($options);
|
||||
} else {
|
||||
$query = '';
|
||||
if (empty($id) === true || preg_match('!^[0-9]*$!', $id) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = 'https://player.vimeo.com/video/' . $id . $query;
|
||||
// append query params
|
||||
foreach ($options as $key => $value) {
|
||||
$query->$key = $value;
|
||||
}
|
||||
|
||||
return static::iframe($url, array_merge(['allowfullscreen' => true], $attr));
|
||||
// build the full video src URL
|
||||
$src = 'https://player.vimeo.com/video/' . $id . $query->toString(true);
|
||||
|
||||
return static::iframe($src, static::videoAttr($attr));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,102 +524,78 @@ class Html extends Xml
|
||||
* @param string $url YouTube video URL
|
||||
* @param array $options Query params for the embed URL
|
||||
* @param array $attr Additional attributes for the `<iframe>` tag
|
||||
* @return string The generated HTML
|
||||
* @return string|null The generated HTML
|
||||
*/
|
||||
public static function youtube(string $url, array $options = [], array $attr = []): string
|
||||
public static function youtube(string $url, array $options = [], array $attr = []): ?string
|
||||
{
|
||||
// default YouTube embed domain
|
||||
$domain = 'youtube.com';
|
||||
$uri = 'embed/';
|
||||
$id = null;
|
||||
$urlOptions = [];
|
||||
if (preg_match('!youtu!i', $url) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$schemes = [
|
||||
// https://www.youtube.com/embed/videoseries?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube.com\/embed\/videoseries\?list=([a-zA-Z0-9_-]+)',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
$uri = new Uri($url);
|
||||
$path = $uri->path();
|
||||
$query = $uri->query();
|
||||
$first = $path->first();
|
||||
$second = $path->nth(1);
|
||||
$host = 'https://' . $uri->host() . '/embed';
|
||||
$src = null;
|
||||
|
||||
// https://www.youtube-nocookie.com/embed/videoseries?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/embed\/videoseries\?list=([a-zA-Z0-9_-]+)',
|
||||
'domain' => 'www.youtube-nocookie.com',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
|
||||
// https://www.youtube.com/embed/d9NF2edxy-M
|
||||
// https://www.youtube.com/embed/d9NF2edxy-M?start=10
|
||||
['pattern' => 'youtube.com\/embed\/([a-zA-Z0-9_-]+)(?:\?(.+))?'],
|
||||
|
||||
// https://www.youtube-nocookie.com/embed/d9NF2edxy-M
|
||||
// https://www.youtube-nocookie.com/embed/d9NF2edxy-M?start=10
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/embed\/([a-zA-Z0-9_-]+)(?:\?(.+))?',
|
||||
'domain' => 'www.youtube-nocookie.com'
|
||||
],
|
||||
|
||||
// https://www.youtube-nocookie.com/watch?v=d9NF2edxy-M
|
||||
// https://www.youtube-nocookie.com/watch?v=d9NF2edxy-M&t=10
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/watch\?v=([a-zA-Z0-9_-]+)(?:&(.+))?',
|
||||
'domain' => 'www.youtube-nocookie.com'
|
||||
],
|
||||
|
||||
// https://www.youtube-nocookie.com/playlist?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/playlist\?list=([a-zA-Z0-9_-]+)',
|
||||
'domain' => 'www.youtube-nocookie.com',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
|
||||
// https://www.youtube.com/watch?v=d9NF2edxy-M
|
||||
// https://www.youtube.com/watch?v=d9NF2edxy-M&t=10
|
||||
['pattern' => 'youtube.com\/watch\?v=([a-zA-Z0-9_-]+)(?:&(.+))?'],
|
||||
|
||||
// https://www.youtube.com/playlist?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube.com\/playlist\?list=([a-zA-Z0-9_-]+)',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
|
||||
// https://youtu.be/d9NF2edxy-M
|
||||
// https://youtu.be/d9NF2edxy-M?t=10
|
||||
['pattern' => 'youtu.be\/([a-zA-Z0-9_-]+)(?:\?(.+))?']
|
||||
];
|
||||
|
||||
foreach ($schemes as $schema) {
|
||||
if (preg_match('!' . $schema['pattern'] . '!i', $url, $array) === 1) {
|
||||
$domain = $schema['domain'] ?? $domain;
|
||||
$uri = $schema['uri'] ?? $uri;
|
||||
$id = $array[1];
|
||||
if (isset($array[2]) === true) {
|
||||
parse_str($array[2], $urlOptions);
|
||||
|
||||
// convert video URL options to embed URL options
|
||||
if (isset($urlOptions['t']) === true) {
|
||||
$urlOptions['start'] = $urlOptions['t'];
|
||||
unset($urlOptions['t']);
|
||||
}
|
||||
}
|
||||
break;
|
||||
$isYoutubeId = function (?string $id = null): bool {
|
||||
if (empty($id) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('!^[a-zA-Z0-9_-]+$!', $id);
|
||||
};
|
||||
|
||||
switch ($path->toString()) {
|
||||
// playlists
|
||||
case 'embed/videoseries':
|
||||
case 'playlist':
|
||||
if ($isYoutubeId($query->list) === true) {
|
||||
$src = $host . '/videoseries';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// regular video URLs
|
||||
case 'watch':
|
||||
if ($isYoutubeId($query->v) === true) {
|
||||
$src = $host . '/' . $query->v;
|
||||
|
||||
$query->start = $query->t;
|
||||
unset($query->v, $query->t);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// short URLs
|
||||
if (Str::contains($uri->host(), 'youtu.be') === true && $isYoutubeId($first) === true) {
|
||||
$src = 'https://www.youtube.com/embed/' . $first;
|
||||
|
||||
$query->start = $query->t;
|
||||
unset($query->t);
|
||||
|
||||
// embedded video URLs
|
||||
} elseif ($first === 'embed' && $isYoutubeId($second) === true) {
|
||||
$src = $host . '/' . $second;
|
||||
}
|
||||
}
|
||||
|
||||
// no match
|
||||
if ($id === null) {
|
||||
throw new Exception('Invalid YouTube source');
|
||||
if (empty($src) === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// build the options query
|
||||
if (empty($options) === false || empty($urlOptions) === false) {
|
||||
$query = (Str::contains($uri, '?') === true ? '&' : '?') . http_build_query(array_merge($urlOptions, $options));
|
||||
} else {
|
||||
$query = '';
|
||||
// append all query parameters
|
||||
foreach ($options as $key => $value) {
|
||||
$query->$key = $value;
|
||||
}
|
||||
|
||||
$url = 'https://' . $domain . '/' . $uri . $id . $query;
|
||||
// build the full video src URL
|
||||
$src = $src . $query->toString(true);
|
||||
|
||||
return static::iframe($url, array_merge(['allowfullscreen' => true], $attr));
|
||||
// render the iframe
|
||||
return static::iframe($src, static::videoAttr($attr));
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ class I18n
|
||||
/**
|
||||
* Current locale
|
||||
*
|
||||
* @var string
|
||||
* @var string|\Closure
|
||||
*/
|
||||
public static $locale = 'en';
|
||||
|
||||
@@ -41,7 +41,7 @@ class I18n
|
||||
* The fallback locale or a
|
||||
* list of fallback locales
|
||||
*
|
||||
* @var string|array
|
||||
* @var string|array|\Closure
|
||||
*/
|
||||
public static $fallback = ['en'];
|
||||
|
||||
@@ -56,14 +56,16 @@ class I18n
|
||||
* Returns the first fallback locale
|
||||
*
|
||||
* @deprecated 3.5.1 Use `\Kirby\Toolkit\I18n::fallbacks()` instead
|
||||
* @todo Add deprecated() helper warning in 3.6.0
|
||||
* @todo Remove in 3.7.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function fallback(): string
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
deprecated('I18n::fallback() has been deprecated. Use I18n::fallbacks() instead.');
|
||||
return static::fallbacks()[0];
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +207,11 @@ class I18n
|
||||
}
|
||||
|
||||
$template = static::translate($key, $fallback, $locale);
|
||||
return Str::template($template, $replace, '-', '{', '}');
|
||||
return Str::template($template, $replace, [
|
||||
'fallback' => '-',
|
||||
'start' => '{',
|
||||
'end' => '}'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +277,7 @@ class I18n
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $count
|
||||
* @param string $locale
|
||||
* @param string|null $locale
|
||||
* @param bool $formatNumber If set to `false`, the count is not formatted
|
||||
* @return mixed
|
||||
*/
|
||||
|
@@ -98,7 +98,7 @@ class Iterator implements IteratorAggregate
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the cusor to the first element
|
||||
* Moves the cursor to the first element
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
|
@@ -1,343 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* The `Mime` class provides method
|
||||
* for MIME type detection or guessing
|
||||
* from different criteria like
|
||||
* extensions etc.
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
class Mime
|
||||
{
|
||||
/**
|
||||
* Extension to MIME type map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $types = [
|
||||
'ai' => 'application/postscript',
|
||||
'aif' => 'audio/x-aiff',
|
||||
'aifc' => 'audio/x-aiff',
|
||||
'aiff' => 'audio/x-aiff',
|
||||
'avi' => 'video/x-msvideo',
|
||||
'avif' => 'image/avif',
|
||||
'bmp' => 'image/bmp',
|
||||
'css' => 'text/css',
|
||||
'csv' => ['text/csv', 'text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream'],
|
||||
'doc' => 'application/msword',
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'dvi' => 'application/x-dvi',
|
||||
'eml' => 'message/rfc822',
|
||||
'eps' => 'application/postscript',
|
||||
'exe' => ['application/octet-stream', 'application/x-msdownload'],
|
||||
'gif' => 'image/gif',
|
||||
'gtar' => 'application/x-gtar',
|
||||
'gz' => 'application/x-gzip',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'ico' => 'image/x-icon',
|
||||
'ics' => 'text/calendar',
|
||||
'js' => 'application/x-javascript',
|
||||
'json' => ['application/json', 'text/json'],
|
||||
'j2k' => ['image/jp2'],
|
||||
'jp2' => ['image/jp2'],
|
||||
'jpg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpeg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpe' => ['image/jpeg', 'image/pjpeg'],
|
||||
'log' => ['text/plain', 'text/x-log'],
|
||||
'm4a' => 'audio/mp4',
|
||||
'm4v' => 'video/mp4',
|
||||
'mid' => 'audio/midi',
|
||||
'midi' => 'audio/midi',
|
||||
'mif' => 'application/vnd.mif',
|
||||
'mov' => 'video/quicktime',
|
||||
'movie' => 'video/x-sgi-movie',
|
||||
'mp2' => 'audio/mpeg',
|
||||
'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'],
|
||||
'mp4' => 'video/mp4',
|
||||
'mpe' => 'video/mpeg',
|
||||
'mpeg' => 'video/mpeg',
|
||||
'mpg' => 'video/mpeg',
|
||||
'mpga' => 'audio/mpeg',
|
||||
'odc' => 'application/vnd.oasis.opendocument.chart',
|
||||
'odp' => 'application/vnd.oasis.opendocument.presentation',
|
||||
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||
'pdf' => ['application/pdf', 'application/x-download'],
|
||||
'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'],
|
||||
'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'],
|
||||
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
'ps' => 'application/postscript',
|
||||
'psd' => 'application/x-photoshop',
|
||||
'qt' => 'video/quicktime',
|
||||
'rss' => 'application/rss+xml',
|
||||
'rtf' => 'text/rtf',
|
||||
'rtx' => 'text/richtext',
|
||||
'shtml' => 'text/html',
|
||||
'svg' => 'image/svg+xml',
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'tar' => 'application/x-tar',
|
||||
'text' => 'text/plain',
|
||||
'txt' => 'text/plain',
|
||||
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'wav' => 'audio/x-wav',
|
||||
'wbxml' => 'application/wbxml',
|
||||
'webm' => 'video/webm',
|
||||
'webp' => 'image/webp',
|
||||
'word' => ['application/msword', 'application/octet-stream'],
|
||||
'xhtml' => 'application/xhtml+xml',
|
||||
'xht' => 'application/xhtml+xml',
|
||||
'xml' => 'text/xml',
|
||||
'xl' => 'application/excel',
|
||||
'xls' => ['application/excel', 'application/vnd.ms-excel', 'application/msexcel'],
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
'xsl' => 'text/xml',
|
||||
'yaml' => ['application/yaml', 'text/yaml'],
|
||||
'yml' => ['application/yaml', 'text/yaml'],
|
||||
'zip' => ['application/x-zip', 'application/zip', 'application/x-zip-compressed'],
|
||||
];
|
||||
|
||||
/**
|
||||
* 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 $mime = null, string $extension = null)
|
||||
{
|
||||
// fixing map
|
||||
$map = [
|
||||
'text/html' => [
|
||||
'svg' => ['Kirby\Toolkit\Mime', 'fromSvg'],
|
||||
],
|
||||
'text/plain' => [
|
||||
'css' => 'text/css',
|
||||
'json' => 'application/json',
|
||||
'svg' => ['Kirby\Toolkit\Mime', 'fromSvg'],
|
||||
],
|
||||
'text/x-asm' => [
|
||||
'css' => 'text/css'
|
||||
],
|
||||
'image/svg' => [
|
||||
'svg' => 'image/svg+xml'
|
||||
]
|
||||
];
|
||||
|
||||
if ($mode = ($map[$mime][$extension] ?? null)) {
|
||||
if (is_callable($mode) === true) {
|
||||
return $mode($file, $mime, $extension);
|
||||
}
|
||||
|
||||
if (is_string($mode) === true) {
|
||||
return $mode;
|
||||
}
|
||||
}
|
||||
|
||||
return $mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses a MIME type by extension
|
||||
*
|
||||
* @param string $extension
|
||||
* @return string|null
|
||||
*/
|
||||
public static function fromExtension(string $extension)
|
||||
{
|
||||
$mime = static::$types[$extension] ?? null;
|
||||
return is_array($mime) === true ? array_shift($mime) : $mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MIME type of a file
|
||||
*
|
||||
* @param string $file
|
||||
* @return string|false
|
||||
*/
|
||||
public static function fromFileInfo(string $file)
|
||||
{
|
||||
if (function_exists('finfo_file') === true && file_exists($file) === true) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mime = finfo_file($finfo, $file);
|
||||
finfo_close($finfo);
|
||||
return $mime;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MIME type of a file
|
||||
*
|
||||
* @param string $file
|
||||
* @return string|false
|
||||
*/
|
||||
public static function fromMimeContentType(string $file)
|
||||
{
|
||||
if (function_exists('mime_content_type') === true && file_exists($file) === true) {
|
||||
return mime_content_type($file);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to detect a valid SVG and returns the MIME type accordingly
|
||||
*
|
||||
* @param string $file
|
||||
* @return string|false
|
||||
*/
|
||||
public static function fromSvg(string $file)
|
||||
{
|
||||
if (file_exists($file) === true) {
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
$svg = new SimpleXMLElement(file_get_contents($file));
|
||||
|
||||
if ($svg !== false && $svg->getName() === 'svg') {
|
||||
return 'image/svg+xml';
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$accepted = Str::accepted($pattern);
|
||||
|
||||
foreach ($accepted as $m) {
|
||||
if (static::matches($mime, $m['value']) === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return fnmatch($wildcard, $test, FNM_PATHNAME) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension for a given MIME type
|
||||
*
|
||||
* @param string|null $mime
|
||||
* @return 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) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
if ($value === $mime) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available extensions for a given MIME type
|
||||
*
|
||||
* @param string|null $mime
|
||||
* @return array
|
||||
*/
|
||||
public static function toExtensions(string $mime = null): array
|
||||
{
|
||||
$extensions = [];
|
||||
|
||||
foreach (static::$types as $key => $value) {
|
||||
if (is_array($value) === true && in_array($mime, $value) === true) {
|
||||
$extensions[] = $key;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === $mime) {
|
||||
$extensions[] = $key;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MIME type of a file
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $extension
|
||||
* @return string|false
|
||||
*/
|
||||
public static function type(string $file, string $extension = null)
|
||||
{
|
||||
// use the standard finfo extension
|
||||
$mime = static::fromFileInfo($file);
|
||||
|
||||
// use the mime_content_type function
|
||||
if ($mime === false) {
|
||||
$mime = static::fromMimeContentType($file);
|
||||
}
|
||||
|
||||
// get the extension or extract it from the filename
|
||||
$extension = $extension ?? F::extension($file);
|
||||
|
||||
// try to guess the mime type at least
|
||||
if ($mime === false) {
|
||||
$mime = static::fromExtension($extension);
|
||||
}
|
||||
|
||||
// fix broken mime detection
|
||||
return static::fix($file, $mime, $extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all detectable MIME types
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function types(): array
|
||||
{
|
||||
return static::$types;
|
||||
}
|
||||
}
|
@@ -157,7 +157,7 @@ class Str
|
||||
foreach ($items as $quality => $values) {
|
||||
foreach ($values as $value) {
|
||||
$result[] = [
|
||||
'quality' => $quality,
|
||||
'quality' => (float)$quality,
|
||||
'value' => $value
|
||||
];
|
||||
}
|
||||
@@ -407,8 +407,12 @@ class Str
|
||||
*
|
||||
* @param string|null $string
|
||||
* @return bool
|
||||
* @deprecated 3.6.0 use `Kirby\Toolkit\V::url()` instead
|
||||
* @todo Throw deprecation warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function isURL(string $string = null): bool
|
||||
public static function isURL(?string $string = null): bool
|
||||
{
|
||||
return filter_var($string, FILTER_VALIDATE_URL) !== false;
|
||||
}
|
||||
@@ -474,14 +478,12 @@ class Str
|
||||
foreach ($type as $t) {
|
||||
$pool = array_merge($pool, static::pool($t));
|
||||
}
|
||||
|
||||
return $pool;
|
||||
} else {
|
||||
switch ($type) {
|
||||
case 'alphaLower':
|
||||
switch (strtolower($type)) {
|
||||
case 'alphalower':
|
||||
$pool = range('a', 'z');
|
||||
break;
|
||||
case 'alphaUpper':
|
||||
case 'alphaupper':
|
||||
$pool = range('A', 'Z');
|
||||
break;
|
||||
case 'alpha':
|
||||
@@ -490,7 +492,7 @@ class Str
|
||||
case 'num':
|
||||
$pool = range(0, 9);
|
||||
break;
|
||||
case 'alphaNum':
|
||||
case 'alphanum':
|
||||
$pool = static::pool(['alpha', 'num']);
|
||||
break;
|
||||
}
|
||||
@@ -728,6 +730,53 @@ class Str
|
||||
return preg_replace('!(' . preg_quote($trim) . ')+$!', '', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces placeholders in string with values from the data array
|
||||
* and escapes HTML in the results in `{{ }}` placeholders
|
||||
* while leaving HTML special characters untouched in `{< >}` placeholders
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string|null $string The string with placeholders
|
||||
* @param array $data Associative array with placeholders as
|
||||
* keys and replacements as values.
|
||||
* Supports query syntax.
|
||||
* @param array $options An options array that contains:
|
||||
* - fallback: if a token does not have any matches
|
||||
* - callback: to be able to handle each matching result (escaping is applied after the callback)
|
||||
*
|
||||
* @return string The filled-in and partially escaped string
|
||||
*/
|
||||
public static function safeTemplate(string $string = null, array $data = [], array $options = []): string
|
||||
{
|
||||
$callback = is_a(($options['callback'] ?? null), 'Closure') === true ? $options['callback'] : null;
|
||||
$fallback = $options['fallback'] ?? '';
|
||||
|
||||
// replace and escape
|
||||
$string = static::template($string, $data, [
|
||||
'start' => '{{',
|
||||
'end' => '}}',
|
||||
'callback' => function ($result, $query, $data) use ($callback) {
|
||||
if ($callback !== null) {
|
||||
$result = $callback($result, $query, $data);
|
||||
}
|
||||
|
||||
return Escape::html($result);
|
||||
},
|
||||
'fallback' => $fallback
|
||||
]);
|
||||
|
||||
// replace unescaped (specifically marked placeholders)
|
||||
$string = static::template($string, $data, [
|
||||
'start' => '{<',
|
||||
'end' => '>}',
|
||||
'callback' => $callback,
|
||||
'fallback' => $fallback
|
||||
]);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortens a string and adds an ellipsis if the string is too long
|
||||
*
|
||||
@@ -765,44 +814,6 @@ class Str
|
||||
return static::substr($string, 0, $length) . $appendix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to a safe version to be used in a URL
|
||||
*
|
||||
* @param string $string The unsafe string
|
||||
* @param string $separator To be used instead of space and
|
||||
* other non-word characters.
|
||||
* @param string $allowed List of all allowed characters (regex)
|
||||
* @param int $maxlength The maximum length of the slug
|
||||
* @return string The safe string
|
||||
*/
|
||||
public static function slug(string $string = null, string $separator = null, string $allowed = null, int $maxlength = 128): string
|
||||
{
|
||||
$separator = $separator ?? static::$defaults['slug']['separator'];
|
||||
$allowed = $allowed ?? static::$defaults['slug']['allowed'];
|
||||
|
||||
$string = trim($string);
|
||||
$string = static::lower($string);
|
||||
$string = static::ascii($string);
|
||||
|
||||
// replace spaces with simple dashes
|
||||
$string = preg_replace('![^' . $allowed . ']!i', $separator, $string);
|
||||
|
||||
if (strlen($separator) > 0) {
|
||||
// remove double separators
|
||||
$string = preg_replace('![' . preg_quote($separator) . ']{2,}!', $separator, $string);
|
||||
}
|
||||
|
||||
// replace slashes with dashes
|
||||
$string = str_replace('/', $separator, $string);
|
||||
|
||||
// trim leading and trailing non-word-chars
|
||||
$string = preg_replace('!^[^a-z0-9]+!', '', $string);
|
||||
$string = preg_replace('![^a-z0-9]+$!', '', $string);
|
||||
|
||||
// cut the string after the given maxlength
|
||||
return static::short($string, $maxlength, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the similarity between two strings with multibyte support
|
||||
* @since 3.5.2
|
||||
@@ -876,6 +887,44 @@ class Str
|
||||
return compact('matches', 'percent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to a safe version to be used in a URL
|
||||
*
|
||||
* @param string $string The unsafe string
|
||||
* @param string $separator To be used instead of space and
|
||||
* other non-word characters.
|
||||
* @param string $allowed List of all allowed characters (regex)
|
||||
* @param int $maxlength The maximum length of the slug
|
||||
* @return string The safe string
|
||||
*/
|
||||
public static function slug(string $string = null, string $separator = null, string $allowed = null, int $maxlength = 128): string
|
||||
{
|
||||
$separator = $separator ?? static::$defaults['slug']['separator'];
|
||||
$allowed = $allowed ?? static::$defaults['slug']['allowed'];
|
||||
|
||||
$string = trim($string);
|
||||
$string = static::lower($string);
|
||||
$string = static::ascii($string);
|
||||
|
||||
// replace spaces with simple dashes
|
||||
$string = preg_replace('![^' . $allowed . ']!i', $separator, $string);
|
||||
|
||||
if (strlen($separator) > 0) {
|
||||
// remove double separators
|
||||
$string = preg_replace('![' . preg_quote($separator) . ']{2,}!', $separator, $string);
|
||||
}
|
||||
|
||||
// replace slashes with dashes
|
||||
$string = str_replace('/', $separator, $string);
|
||||
|
||||
// trim leading and trailing non-word-chars
|
||||
$string = preg_replace('!^[^a-z0-9]+!', '', $string);
|
||||
$string = preg_replace('![^a-z0-9]+$!', '', $string);
|
||||
|
||||
// cut the string after the given maxlength
|
||||
return static::short($string, $maxlength, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to snake case.
|
||||
*
|
||||
@@ -953,7 +1002,7 @@ class Str
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces placeholders in string with value from array
|
||||
* Replaces placeholders in string with values from the data array
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
@@ -964,23 +1013,33 @@ class Str
|
||||
*
|
||||
* @param string|null $string The string with placeholders
|
||||
* @param array $data Associative array with placeholders as
|
||||
* keys and replacements as values
|
||||
* keys and replacements as values.
|
||||
* Supports query syntax.
|
||||
* @param string|array|null $fallback An options array that contains:
|
||||
* - fallback: if a token does not have any matches
|
||||
* - callback: to be able to handle each matching result
|
||||
* - start: start placeholder
|
||||
* - end: end placeholder
|
||||
* A simple fallback string is supported for compatibility (but deprecated).
|
||||
* @param string $start Placeholder start characters
|
||||
* @param string $end Placeholder end characters
|
||||
* @param string $start Placeholder start characters (deprecated)
|
||||
* @param string $end Placeholder end characters (deprecated)
|
||||
*
|
||||
* @todo Deprecate `string $fallback` and `$start`/`$end` arguments with warning in 3.6.0
|
||||
* @todo Remove `$start` and `$end` parameters, rename `$fallback` to `$options` and only support `array` type for `$options` in 3.7.0
|
||||
*
|
||||
* @return string The filled-in string
|
||||
*/
|
||||
public static function template(string $string = null, array $data = [], $fallback = null, string $start = '{{', string $end = '}}'): string
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (
|
||||
is_string($fallback) === true ||
|
||||
$start !== '{{' ||
|
||||
$end !== '}}'
|
||||
) {
|
||||
deprecated('Str::template(): The $fallback, $start and $end parameters have been deprecated. Please pass an array to the $options parameter instead with `fallback`, `start` or `end` keys: Str::template($string, $data, $options)');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$options = $fallback;
|
||||
$fallback = is_string($options) === true ? $options : ($options['fallback'] ?? null);
|
||||
$callback = is_a(($options['callback'] ?? null), 'Closure') === true ? $options['callback'] : null;
|
||||
@@ -1165,12 +1224,17 @@ class Str
|
||||
*/
|
||||
public static function widont(string $string = null): string
|
||||
{
|
||||
return preg_replace_callback('|([^\s])\s+([^\s]+)\s*$|u', function ($matches) {
|
||||
// Replace space between last word and punctuation
|
||||
$string = preg_replace_callback('|(\S)\s(\S?)$|u', function ($matches) {
|
||||
return $matches[1] . ' ' . $matches[2];
|
||||
}, $string);
|
||||
|
||||
// Replace space between last two words
|
||||
return preg_replace_callback('|(\s)(?=\S*$)(\S+)|u', function ($matches) {
|
||||
if (static::contains($matches[2], '-')) {
|
||||
return $matches[1] . ' ' . str_replace('-', '‑', $matches[2]);
|
||||
} else {
|
||||
return $matches[1] . ' ' . $matches[2];
|
||||
$matches[2] = str_replace('-', '‑', $matches[2]);
|
||||
}
|
||||
return ' ' . $matches[2];
|
||||
}, $string);
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Kirby\Filesystem\F;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
|
@@ -71,9 +71,9 @@ class V
|
||||
|
||||
if (is_array($value) === true) {
|
||||
try {
|
||||
foreach ($value as $index => $item) {
|
||||
foreach ($value as $key => $item) {
|
||||
if (is_array($item) === true) {
|
||||
$value[$index] = implode('|', $item);
|
||||
$value[$key] = implode('|', $item);
|
||||
}
|
||||
}
|
||||
$value = implode(', ', $value);
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Filesystem\F;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
|
@@ -129,7 +129,7 @@ class Xml
|
||||
* @param string $name The name of the root element
|
||||
* @param bool $head Include the XML declaration head or not
|
||||
* @param string $indent Indentation string, defaults to two spaces
|
||||
* @param int $level The indendation level (used internally)
|
||||
* @param int $level The indentation level (used internally)
|
||||
* @return string The XML string
|
||||
*/
|
||||
public static function create($props, string $name = 'root', bool $head = true, string $indent = ' ', int $level = 0): string
|
||||
|
Reference in New Issue
Block a user