Upgrade to 3.6.0
This commit is contained in:
@@ -5,9 +5,9 @@ namespace Kirby\Api;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Http\Router;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Pagination;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
@@ -3,8 +3,8 @@
|
||||
namespace Kirby\Cache;
|
||||
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
|
@@ -16,7 +16,7 @@ use Memcached as MemcachedExt;
|
||||
class MemCached extends Cache
|
||||
{
|
||||
/**
|
||||
* store for the memache connection
|
||||
* store for the memcache connection
|
||||
* @var \Memcached
|
||||
*/
|
||||
protected $connection;
|
||||
|
@@ -3,9 +3,8 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Api\Api as BaseApi;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Form\Form;
|
||||
|
||||
/**
|
||||
* Api
|
||||
@@ -61,10 +60,12 @@ class Api extends BaseApi
|
||||
{
|
||||
$field = Form::for($model)->field($name);
|
||||
|
||||
$fieldApi = $this->clone([
|
||||
'routes' => $field->api(),
|
||||
'data' => array_merge($this->data(), ['field' => $field])
|
||||
]);
|
||||
$fieldApi = new static(
|
||||
array_merge($this->propertyData, [
|
||||
'data' => array_merge($this->data(), ['field' => $field]),
|
||||
'routes' => $field->api(),
|
||||
]),
|
||||
);
|
||||
|
||||
return $fieldApi->call($path, $this->requestMethod(), $this->requestData());
|
||||
}
|
||||
@@ -80,19 +81,7 @@ class Api extends BaseApi
|
||||
*/
|
||||
public function file(string $path = null, string $filename)
|
||||
{
|
||||
$filename = urldecode($filename);
|
||||
$file = $this->parent($path)->file($filename);
|
||||
|
||||
if ($file && $file->isReadable() === true) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'file.notFound',
|
||||
'data' => [
|
||||
'filename' => $filename
|
||||
]
|
||||
]);
|
||||
return Find::file($path, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,49 +94,7 @@ class Api extends BaseApi
|
||||
*/
|
||||
public function parent(string $path)
|
||||
{
|
||||
$modelType = in_array($path, ['site', 'account']) ? $path : trim(dirname($path), '/');
|
||||
$modelTypes = [
|
||||
'site' => 'site',
|
||||
'users' => 'user',
|
||||
'pages' => 'page',
|
||||
'account' => 'account'
|
||||
];
|
||||
$modelName = $modelTypes[$modelType] ?? null;
|
||||
|
||||
if (Str::endsWith($modelType, '/files') === true) {
|
||||
$modelName = 'file';
|
||||
}
|
||||
|
||||
$kirby = $this->kirby();
|
||||
|
||||
switch ($modelName) {
|
||||
case 'site':
|
||||
$model = $kirby->site();
|
||||
break;
|
||||
case 'account':
|
||||
$model = $kirby->user(null, $kirby->option('api.allowImpersonation', false));
|
||||
break;
|
||||
case 'page':
|
||||
$id = str_replace(['+', ' '], '/', basename($path));
|
||||
$model = $kirby->page($id);
|
||||
break;
|
||||
case 'file':
|
||||
$model = $this->file(...explode('/files/', $path));
|
||||
break;
|
||||
case 'user':
|
||||
$model = $kirby->user(basename($path));
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid model type: ' . $modelType);
|
||||
}
|
||||
|
||||
if ($model) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => $modelName . '.undefined'
|
||||
]);
|
||||
return Find::parent($path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,19 +126,7 @@ class Api extends BaseApi
|
||||
*/
|
||||
public function page(string $id)
|
||||
{
|
||||
$id = str_replace('+', '/', $id);
|
||||
$page = $this->kirby->page($id);
|
||||
|
||||
if ($page && $page->isReadable() === true) {
|
||||
return $page;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'page.notFound',
|
||||
'data' => [
|
||||
'slug' => $id
|
||||
]
|
||||
]);
|
||||
return Find::page($id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,22 +222,15 @@ class Api extends BaseApi
|
||||
*/
|
||||
public function user(string $id = null)
|
||||
{
|
||||
// get the authenticated user
|
||||
if ($id === null) {
|
||||
return $this->kirby->auth()->user(null, $this->kirby()->option('api.allowImpersonation', false));
|
||||
}
|
||||
try {
|
||||
return Find::user($id);
|
||||
} catch (NotFoundException $e) {
|
||||
if ($id === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get a specific user by id
|
||||
if ($user = $this->kirby->users()->find($id)) {
|
||||
return $user;
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'user.notFound',
|
||||
'data' => [
|
||||
'name' => $id
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,22 +3,23 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Email\PHPMailer as Emailer;
|
||||
use Kirby\Exception\ErrorPageException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Request;
|
||||
use Kirby\Http\Router;
|
||||
use Kirby\Http\Server;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Http\Visitor;
|
||||
use Kirby\Session\AutoSession;
|
||||
use Kirby\Text\KirbyTag;
|
||||
use Kirby\Text\KirbyTags;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Config;
|
||||
use Kirby\Toolkit\Controller;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Throwable;
|
||||
|
||||
@@ -53,6 +54,7 @@ class App
|
||||
|
||||
protected $api;
|
||||
protected $collections;
|
||||
protected $core;
|
||||
protected $defaultLanguage;
|
||||
protected $language;
|
||||
protected $languages;
|
||||
@@ -84,6 +86,8 @@ class App
|
||||
*/
|
||||
public function __construct(array $props = [], bool $setInstance = true)
|
||||
{
|
||||
$this->core = new Core($this);
|
||||
|
||||
// register all roots to be able to load stuff afterwards
|
||||
$this->bakeRoots($props['roots'] ?? []);
|
||||
|
||||
@@ -265,7 +269,7 @@ class App
|
||||
*/
|
||||
protected function bakeRoots(array $roots = null)
|
||||
{
|
||||
$roots = array_merge(require dirname(__DIR__, 2) . '/config/roots.php', (array)$roots);
|
||||
$roots = array_merge($this->core->roots(), (array)$roots);
|
||||
$this->roots = Ingredients::bake($roots);
|
||||
return $this;
|
||||
}
|
||||
@@ -283,7 +287,7 @@ class App
|
||||
$urls['index'] = $this->options['url'];
|
||||
}
|
||||
|
||||
$urls = array_merge(require $this->root('kirby') . '/config/urls.php', (array)$urls);
|
||||
$urls = array_merge($this->core->urls(), (array)$urls);
|
||||
$this->urls = Ingredients::bake($urls);
|
||||
return $this;
|
||||
}
|
||||
@@ -503,6 +507,17 @@ class App
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access to object that lists
|
||||
* all parts of Kirby core
|
||||
*
|
||||
* @return \Kirby\Cms\Core
|
||||
*/
|
||||
public function core()
|
||||
{
|
||||
return $this->core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default language object
|
||||
*
|
||||
@@ -555,11 +570,14 @@ class App
|
||||
*
|
||||
* @param mixed $preset
|
||||
* @param array $props
|
||||
* @return \Kirby\Email\PHPMailer
|
||||
* @return \Kirby\Email\Email
|
||||
*/
|
||||
public function email($preset = [], array $props = [])
|
||||
{
|
||||
return new Emailer((new Email($preset, $props))->toArray(), $props['debug'] ?? false);
|
||||
$debug = $props['debug'] ?? false;
|
||||
$props = (new Email($preset, $props))->toArray();
|
||||
|
||||
return ($this->component('email'))($this, $props, $debug);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -641,12 +659,11 @@ class App
|
||||
// any direct exception will be turned into an error page
|
||||
if (is_a($input, 'Throwable') === true) {
|
||||
if (is_a($input, 'Kirby\Exception\Exception') === true) {
|
||||
$code = $input->getHttpCode();
|
||||
$message = $input->getMessage();
|
||||
$code = $input->getHttpCode();
|
||||
} else {
|
||||
$code = $input->getCode();
|
||||
$message = $input->getMessage();
|
||||
$code = $input->getCode();
|
||||
}
|
||||
$message = $input->getMessage();
|
||||
|
||||
if ($code < 400 || $code > 599) {
|
||||
$code = 500;
|
||||
@@ -748,8 +765,13 @@ class App
|
||||
$data['kirby'] = $data['kirby'] ?? $this;
|
||||
$data['site'] = $data['site'] ?? $data['kirby']->site();
|
||||
$data['parent'] = $data['parent'] ?? $data['site']->page();
|
||||
$options = $this->options;
|
||||
|
||||
return KirbyTags::parse($text, $data, $this->options, $this);
|
||||
$text = $this->apply('kirbytags:before', compact('text', 'data', 'options'), 'text');
|
||||
$text = KirbyTags::parse($text, $data, $options);
|
||||
$text = $this->apply('kirbytags:after', compact('text', 'data', 'options'), 'text');
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -818,17 +840,28 @@ class App
|
||||
/**
|
||||
* Returns all available site languages
|
||||
*
|
||||
* @param bool
|
||||
* @return \Kirby\Cms\Languages
|
||||
*/
|
||||
public function languages()
|
||||
public function languages(bool $clone = true)
|
||||
{
|
||||
if ($this->languages !== null) {
|
||||
return clone $this->languages;
|
||||
return $clone === true ? clone $this->languages : $this->languages;
|
||||
}
|
||||
|
||||
return $this->languages = Languages::load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Access Kirby's part loader
|
||||
*
|
||||
* @return \Kirby\Cms\Loader
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
return new Loader($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app's locks object
|
||||
*
|
||||
@@ -996,6 +1029,10 @@ class App
|
||||
$parent = $parent ?? $this->site();
|
||||
|
||||
if ($page = $parent->find($id)) {
|
||||
/**
|
||||
* We passed a single $id, we can be sure that the result is
|
||||
* @var \Kirby\Cms\Page $page
|
||||
*/
|
||||
return $page;
|
||||
}
|
||||
|
||||
@@ -1213,7 +1250,7 @@ class App
|
||||
}
|
||||
|
||||
$registry = $this->extensions('routes');
|
||||
$system = (include $this->root('kirby') . '/config/routes.php')($this);
|
||||
$system = $this->core->routes();
|
||||
$routes = array_merge($system['before'], $registry, $system['after']);
|
||||
|
||||
return $this->routes = $routes;
|
||||
|
@@ -53,7 +53,7 @@ trait AppCaches
|
||||
// initialize the cache class
|
||||
$cache = new $className($options);
|
||||
|
||||
// check if it is a useable cache object
|
||||
// check if it is a usable cache object
|
||||
if (is_a($cache, 'Kirby\Cache\Cache') !== true) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'app.invalid.cacheType',
|
||||
|
@@ -4,12 +4,15 @@ namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Filesystem\Mime;
|
||||
use Kirby\Form\Field as FormField;
|
||||
use Kirby\Image\Image;
|
||||
use Kirby\Panel\Panel;
|
||||
use Kirby\Text\KirbyTag;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Collection as ToolkitCollection;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
/**
|
||||
@@ -30,13 +33,6 @@ trait AppPlugins
|
||||
*/
|
||||
protected static $plugins = [];
|
||||
|
||||
/**
|
||||
* Cache for system extensions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $systemExtensions = null;
|
||||
|
||||
/**
|
||||
* The extension registry
|
||||
*
|
||||
@@ -48,7 +44,11 @@ trait AppPlugins
|
||||
|
||||
// other plugin types
|
||||
'api' => [],
|
||||
'areas' => [],
|
||||
'authChallenges' => [],
|
||||
'blockMethods' => [],
|
||||
'blockModels' => [],
|
||||
'blocksMethods' => [],
|
||||
'blueprints' => [],
|
||||
'cacheTypes' => [],
|
||||
'collections' => [],
|
||||
@@ -58,9 +58,13 @@ trait AppPlugins
|
||||
'collectionMethods' => [],
|
||||
'fieldMethods' => [],
|
||||
'fileMethods' => [],
|
||||
'fileTypes' => [],
|
||||
'filesMethods' => [],
|
||||
'fields' => [],
|
||||
'hooks' => [],
|
||||
'layoutMethods' => [],
|
||||
'layoutColumnMethods' => [],
|
||||
'layoutsMethods' => [],
|
||||
'pages' => [],
|
||||
'pageMethods' => [],
|
||||
'pagesMethods' => [],
|
||||
@@ -77,7 +81,7 @@ trait AppPlugins
|
||||
'userMethods' => [],
|
||||
'userModels' => [],
|
||||
'usersMethods' => [],
|
||||
'validators' => []
|
||||
'validators' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -126,6 +130,25 @@ trait AppPlugins
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional custom Panel areas
|
||||
*
|
||||
* @param array $areas
|
||||
* @return array
|
||||
*/
|
||||
protected function extendAreas(array $areas): array
|
||||
{
|
||||
foreach ($areas as $id => $area) {
|
||||
if (isset($this->extensions['areas'][$id]) === false) {
|
||||
$this->extensions['areas'][$id] = [];
|
||||
}
|
||||
|
||||
$this->extensions['areas'][$id][] = $area;
|
||||
}
|
||||
|
||||
return $this->extensions['areas'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional authentication challenges
|
||||
*
|
||||
@@ -137,6 +160,39 @@ trait AppPlugins
|
||||
return $this->extensions['authChallenges'] = Auth::$challenges = array_merge(Auth::$challenges, $challenges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional block methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendBlockMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['blockMethods'] = Block::$methods = array_merge(Block::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional block models
|
||||
*
|
||||
* @param array $models
|
||||
* @return array
|
||||
*/
|
||||
protected function extendBlockModels(array $models): array
|
||||
{
|
||||
return $this->extensions['blockModels'] = Block::$models = array_merge(Block::$models, $models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional blocks methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendBlocksMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['blockMethods'] = Blocks::$methods = array_merge(Blocks::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional blueprints
|
||||
*
|
||||
@@ -225,6 +281,59 @@ trait AppPlugins
|
||||
return $this->extensions['fileMethods'] = File::$methods = array_merge(File::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional custom file types and mimes
|
||||
*
|
||||
* @param array $fileTypes
|
||||
* @return array
|
||||
*/
|
||||
protected function extendFileTypes(array $fileTypes): array
|
||||
{
|
||||
// normalize array
|
||||
foreach ($fileTypes as $ext => $file) {
|
||||
$extension = $file['extension'] ?? $ext;
|
||||
$type = $file['type'] ?? null;
|
||||
$mime = $file['mime'] ?? null;
|
||||
$resizable = $file['resizable'] ?? false;
|
||||
$viewable = $file['viewable'] ?? false;
|
||||
|
||||
if (is_string($type) === true) {
|
||||
if (isset(F::$types[$type]) === false) {
|
||||
F::$types[$type] = [];
|
||||
}
|
||||
|
||||
if (in_array($extension, F::$types[$type]) === false) {
|
||||
F::$types[$type][] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mime !== null) {
|
||||
if (array_key_exists($extension, Mime::$types) === true) {
|
||||
// if `Mime::$types[$extension]` is not already an array, make it one
|
||||
// and append the new MIME type unless it's already in the list
|
||||
Mime::$types[$extension] = array_unique(array_merge((array)Mime::$types[$extension], (array)$mime));
|
||||
} else {
|
||||
Mime::$types[$extension] = $mime;
|
||||
}
|
||||
}
|
||||
|
||||
if ($resizable === true && in_array($extension, Image::$resizableTypes) === false) {
|
||||
Image::$resizableTypes[] = $extension;
|
||||
}
|
||||
|
||||
if ($viewable === true && in_array($extension, Image::$viewableTypes) === false) {
|
||||
Image::$viewableTypes[] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->extensions['fileTypes'] = [
|
||||
'type' => F::$types,
|
||||
'mime' => Mime::$types,
|
||||
'resizable' => Image::$resizableTypes,
|
||||
'viewable' => Image::$viewableTypes
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional files methods
|
||||
*
|
||||
@@ -294,6 +403,39 @@ trait AppPlugins
|
||||
return $this->extensions['markdown'] = $markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional layout methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendLayoutMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['layoutMethods'] = Layout::$methods = array_merge(Layout::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional layout column methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendLayoutColumnMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['layoutColumnMethods'] = LayoutColumn::$methods = array_merge(LayoutColumn::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional layouts methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendLayoutsMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['layoutsMethods'] = Layouts::$methods = array_merge(Layouts::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional options
|
||||
*
|
||||
@@ -625,95 +767,27 @@ trait AppPlugins
|
||||
*/
|
||||
protected function extensionsFromSystem()
|
||||
{
|
||||
$root = $this->root('kirby');
|
||||
// mixins
|
||||
FormField::$mixins = $this->core->fieldMixins();
|
||||
Section::$mixins = $this->core->sectionMixins();
|
||||
|
||||
// load static extensions only once
|
||||
if (static::$systemExtensions === null) {
|
||||
// Form Field Mixins
|
||||
FormField::$mixins['datetime'] = include $root . '/config/fields/mixins/datetime.php';
|
||||
FormField::$mixins['filepicker'] = include $root . '/config/fields/mixins/filepicker.php';
|
||||
FormField::$mixins['min'] = include $root . '/config/fields/mixins/min.php';
|
||||
FormField::$mixins['options'] = include $root . '/config/fields/mixins/options.php';
|
||||
FormField::$mixins['pagepicker'] = include $root . '/config/fields/mixins/pagepicker.php';
|
||||
FormField::$mixins['picker'] = include $root . '/config/fields/mixins/picker.php';
|
||||
FormField::$mixins['upload'] = include $root . '/config/fields/mixins/upload.php';
|
||||
FormField::$mixins['userpicker'] = include $root . '/config/fields/mixins/userpicker.php';
|
||||
// aliases
|
||||
KirbyTag::$aliases = $this->core->kirbyTagAliases();
|
||||
Field::$aliases = $this->core->fieldMethodAliases();
|
||||
|
||||
// Tag Aliases
|
||||
KirbyTag::$aliases = [
|
||||
'youtube' => 'video',
|
||||
'vimeo' => 'video'
|
||||
];
|
||||
// blueprint presets
|
||||
PageBlueprint::$presets = $this->core->blueprintPresets();
|
||||
|
||||
// Field method aliases
|
||||
Field::$aliases = [
|
||||
'bool' => 'toBool',
|
||||
'esc' => 'escape',
|
||||
'excerpt' => 'toExcerpt',
|
||||
'float' => 'toFloat',
|
||||
'h' => 'html',
|
||||
'int' => 'toInt',
|
||||
'kt' => 'kirbytext',
|
||||
'kti' => 'kirbytextinline',
|
||||
'link' => 'toLink',
|
||||
'md' => 'markdown',
|
||||
'sp' => 'smartypants',
|
||||
'v' => 'isValid',
|
||||
'x' => 'xml'
|
||||
];
|
||||
|
||||
// blueprint presets
|
||||
PageBlueprint::$presets['pages'] = include $root . '/config/presets/pages.php';
|
||||
PageBlueprint::$presets['page'] = include $root . '/config/presets/page.php';
|
||||
PageBlueprint::$presets['files'] = include $root . '/config/presets/files.php';
|
||||
|
||||
// section mixins
|
||||
Section::$mixins['empty'] = include $root . '/config/sections/mixins/empty.php';
|
||||
Section::$mixins['headline'] = include $root . '/config/sections/mixins/headline.php';
|
||||
Section::$mixins['help'] = include $root . '/config/sections/mixins/help.php';
|
||||
Section::$mixins['layout'] = include $root . '/config/sections/mixins/layout.php';
|
||||
Section::$mixins['max'] = include $root . '/config/sections/mixins/max.php';
|
||||
Section::$mixins['min'] = include $root . '/config/sections/mixins/min.php';
|
||||
Section::$mixins['pagination'] = include $root . '/config/sections/mixins/pagination.php';
|
||||
Section::$mixins['parent'] = include $root . '/config/sections/mixins/parent.php';
|
||||
|
||||
// section types
|
||||
Section::$types['info'] = include $root . '/config/sections/info.php';
|
||||
Section::$types['pages'] = include $root . '/config/sections/pages.php';
|
||||
Section::$types['files'] = include $root . '/config/sections/files.php';
|
||||
Section::$types['fields'] = include $root . '/config/sections/fields.php';
|
||||
|
||||
static::$systemExtensions = [
|
||||
'components' => include $root . '/config/components.php',
|
||||
'blueprints' => include $root . '/config/blueprints.php',
|
||||
'fields' => include $root . '/config/fields.php',
|
||||
'fieldMethods' => include $root . '/config/methods.php',
|
||||
'snippets' => include $root . '/config/snippets.php',
|
||||
'tags' => include $root . '/config/tags.php',
|
||||
'templates' => include $root . '/config/templates.php'
|
||||
];
|
||||
}
|
||||
|
||||
// default auth challenges
|
||||
$this->extendAuthChallenges([
|
||||
'email' => 'Kirby\Cms\Auth\EmailChallenge'
|
||||
]);
|
||||
|
||||
// default cache types
|
||||
$this->extendCacheTypes([
|
||||
'apcu' => 'Kirby\Cache\ApcuCache',
|
||||
'file' => 'Kirby\Cache\FileCache',
|
||||
'memcached' => 'Kirby\Cache\MemCached',
|
||||
'memory' => 'Kirby\Cache\MemoryCache',
|
||||
]);
|
||||
|
||||
$this->extendComponents(static::$systemExtensions['components']);
|
||||
$this->extendBlueprints(static::$systemExtensions['blueprints']);
|
||||
$this->extendFields(static::$systemExtensions['fields']);
|
||||
$this->extendFieldMethods((static::$systemExtensions['fieldMethods'])($this));
|
||||
$this->extendSnippets(static::$systemExtensions['snippets']);
|
||||
$this->extendTags(static::$systemExtensions['tags']);
|
||||
$this->extendTemplates(static::$systemExtensions['templates']);
|
||||
$this->extendAuthChallenges($this->core->authChallenges());
|
||||
$this->extendCacheTypes($this->core->cacheTypes());
|
||||
$this->extendComponents($this->core->components());
|
||||
$this->extendBlueprints($this->core->blueprints());
|
||||
$this->extendFields($this->core->fields());
|
||||
$this->extendFieldMethods($this->core->fieldMethods());
|
||||
$this->extendSections($this->core->sections());
|
||||
$this->extendSnippets($this->core->snippets());
|
||||
$this->extendTags($this->core->kirbyTags());
|
||||
$this->extendTemplates($this->core->templates());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -725,7 +799,7 @@ trait AppPlugins
|
||||
*/
|
||||
public function nativeComponent(string $component)
|
||||
{
|
||||
return static::$systemExtensions['components'][$component] ?? false;
|
||||
return $this->core->components()[$component] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -159,20 +159,23 @@ trait AppTranslations
|
||||
* Set locale settings
|
||||
*
|
||||
* @deprecated 3.5.0 Use `\Kirby\Toolkit\Locale::set()` instead
|
||||
* @todo Remove in 3.6.0
|
||||
* @todo Remove in 3.7.0
|
||||
*
|
||||
* @param string|array $locale
|
||||
*/
|
||||
public function setLocale($locale): void
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
deprecated('`Kirby\Cms\App::setLocale()` has been deprecated and will be removed in 3.7.0. Use `Kirby\Toolkit\Locale::set()` instead');
|
||||
Locale::set($locale);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a specific translation by locale
|
||||
*
|
||||
* @param string|null $locale Locale name or `null` for the current locale
|
||||
* @return \Kirby\Cms\Translation|null
|
||||
* @return \Kirby\Cms\Translation
|
||||
*/
|
||||
public function translation(?string $locale = null)
|
||||
{
|
||||
|
@@ -8,10 +8,10 @@ use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Idn;
|
||||
use Kirby\Http\Request\Auth\BasicAuth;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\F;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -95,21 +95,7 @@ class Auth
|
||||
*/
|
||||
public function createChallenge(string $email, bool $long = false, string $mode = 'login')
|
||||
{
|
||||
// ensure that email addresses with IDN domains are in Unicode format
|
||||
$email = Idn::decodeEmail($email);
|
||||
|
||||
if ($this->isBlocked($email) === true) {
|
||||
$this->kirby->trigger('user.login:failed', compact('email'));
|
||||
|
||||
if ($this->kirby->option('debug') === true) {
|
||||
$message = 'Rate limit exceeded';
|
||||
} else {
|
||||
// avoid leaking security-relevant information
|
||||
$message = ['key' => 'access.login'];
|
||||
}
|
||||
|
||||
throw new PermissionException($message);
|
||||
}
|
||||
$email = $this->validateEmail($email);
|
||||
|
||||
// rate-limit the number of challenges for DoS/DDoS protection
|
||||
$this->track($email, false);
|
||||
@@ -190,7 +176,7 @@ class Auth
|
||||
$fromHeader = $this->kirby->request()->csrf();
|
||||
|
||||
// check for a predefined csrf or use the one from session
|
||||
$fromSession = $this->kirby->option('api.csrf', csrf());
|
||||
$fromSession = $this->csrfFromSession();
|
||||
|
||||
// compare both tokens
|
||||
if (hash_equals((string)$fromSession, (string)$fromHeader) !== true) {
|
||||
@@ -200,6 +186,18 @@ class Auth
|
||||
return $fromSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either predefined csrf or the one from session
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function csrfFromSession(): string
|
||||
{
|
||||
$isDev = $this->kirby->option('panel.dev', false) !== false;
|
||||
return $this->kirby->option('api.csrf', $isDev ? 'dev' : csrf());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logged in user by checking
|
||||
* for a basic authentication header with
|
||||
@@ -384,7 +382,7 @@ class Auth
|
||||
* @param bool $long
|
||||
* @return \Kirby\Cms\User
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occurred with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the email was invalid
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`)
|
||||
*/
|
||||
@@ -415,7 +413,7 @@ class Auth
|
||||
* @param bool $long
|
||||
* @return \Kirby\Cms\Auth\Status
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occurred with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the email was invalid
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`)
|
||||
*/
|
||||
@@ -493,18 +491,15 @@ class Auth
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the user credentials and returns the user object on success;
|
||||
* otherwise logs the failed attempt
|
||||
* Ensures that email addresses with IDN domains are in Unicode format
|
||||
* and that the rate limit was not exceeded
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @return \Kirby\Cms\User
|
||||
* @return string The normalized Unicode email address
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the email was invalid
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`)
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded
|
||||
*/
|
||||
public function validatePassword(string $email, string $password)
|
||||
protected function validateEmail(string $email): string
|
||||
{
|
||||
// ensure that email addresses with IDN domains are in Unicode format
|
||||
$email = Idn::decodeEmail($email);
|
||||
@@ -523,6 +518,25 @@ class Auth
|
||||
throw new PermissionException($message);
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the user credentials and returns the user object on success;
|
||||
* otherwise logs the failed attempt
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @return \Kirby\Cms\User
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occurred with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the email was invalid
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`)
|
||||
*/
|
||||
public function validatePassword(string $email, string $password)
|
||||
{
|
||||
$email = $this->validateEmail($email);
|
||||
|
||||
// validate the user
|
||||
try {
|
||||
if ($user = $this->kirby->users()->find($email)) {
|
||||
@@ -724,7 +738,7 @@ class Auth
|
||||
* logged in user will be returned
|
||||
* @return \Kirby\Cms\User|null
|
||||
*
|
||||
* @throws \Throwable If an authentication error occured
|
||||
* @throws \Throwable If an authentication error occurred
|
||||
*/
|
||||
public function user($session = null, bool $allowImpersonation = true)
|
||||
{
|
||||
@@ -770,7 +784,7 @@ class Auth
|
||||
* @return \Kirby\Cms\User User object of the logged-in user
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded, the challenge timed out, the code
|
||||
* is incorrect or if any other error occured with debug mode off
|
||||
* is incorrect or if any other error occurred with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the user from the challenge doesn't exist
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If no authentication challenge is active
|
||||
* @throws \Kirby\Exception\LogicException If the authentication challenge is invalid
|
||||
@@ -830,7 +844,7 @@ class Auth
|
||||
|
||||
throw new LogicException('Invalid authentication challenge: ' . $challenge);
|
||||
} catch (Throwable $e) {
|
||||
if ($e->getMessage() !== 'Rate limit exceeded') {
|
||||
if (empty($email) === false && $e->getMessage() !== 'Rate limit exceeded') {
|
||||
$this->track($email);
|
||||
}
|
||||
|
||||
|
@@ -66,6 +66,7 @@ class EmailChallenge extends Challenge
|
||||
'template' => 'auth/' . $mode,
|
||||
'data' => [
|
||||
'user' => $user,
|
||||
'site' => $kirby->system()->title(),
|
||||
'code' => $formatted,
|
||||
'timeout' => round($options['timeout'] / 60)
|
||||
]
|
||||
|
@@ -22,6 +22,8 @@ class Block extends Item
|
||||
{
|
||||
const ITEMS_CLASS = '\Kirby\Cms\Blocks';
|
||||
|
||||
use HasMethods;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Content
|
||||
*/
|
||||
@@ -32,6 +34,13 @@ class Block extends Item
|
||||
*/
|
||||
protected $isHidden;
|
||||
|
||||
/**
|
||||
* Registry with all block models
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $models = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -46,6 +55,11 @@ class Block extends Item
|
||||
*/
|
||||
public function __call(string $method, array $args = [])
|
||||
{
|
||||
// block methods
|
||||
if ($this->hasMethod($method)) {
|
||||
return $this->callMethod($method, $args);
|
||||
}
|
||||
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
@@ -53,7 +67,7 @@ class Block extends Item
|
||||
* Creates a new block object
|
||||
*
|
||||
* @param array $params
|
||||
* @param \Kirby\Cms\Blocks $siblings
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $params)
|
||||
{
|
||||
@@ -69,7 +83,7 @@ class Block extends Item
|
||||
|
||||
$this->content = $params['content'] ?? [];
|
||||
$this->isHidden = $params['isHidden'] ?? false;
|
||||
$this->type = $params['type'] ?? null;
|
||||
$this->type = $params['type'];
|
||||
|
||||
// create the content object
|
||||
$this->content = new Content($this->content, $this->parent);
|
||||
@@ -89,13 +103,13 @@ class Block extends Item
|
||||
* Deprecated method to return the block type
|
||||
*
|
||||
* @deprecated 3.5.0 Use `\Kirby\Cms\Block::type()` instead
|
||||
* @todo Add deprecated() helper warning in 3.6.0
|
||||
* @todo Remove in 3.7.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function _key(): string
|
||||
{
|
||||
deprecated('Block::_key() has been deprecated. Use Block::type() instead.');
|
||||
return $this->type();
|
||||
}
|
||||
|
||||
@@ -103,13 +117,13 @@ class Block extends Item
|
||||
* Deprecated method to return the block id
|
||||
*
|
||||
* @deprecated 3.5.0 Use `\Kirby\Cms\Block::id()` instead
|
||||
* @todo Add deprecated() helper warning in 3.6.0
|
||||
* @todo Remove in 3.7.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function _uid(): string
|
||||
{
|
||||
deprecated('Block::_uid() has been deprecated. Use Block::id() instead.');
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
@@ -154,6 +168,38 @@ class Block extends Item
|
||||
return Str::excerpt($this->toHtml(), ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a block object with registering blocks models
|
||||
*
|
||||
* @param array $params
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public static function factory(array $params)
|
||||
{
|
||||
$type = $params['type'] ?? null;
|
||||
|
||||
if (empty($type) === false && $class = (static::$models[$type] ?? null)) {
|
||||
$object = new $class($params);
|
||||
|
||||
if (is_a($object, 'Kirby\Cms\Block') === true) {
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
// default model for blocks
|
||||
if ($class = (static::$models['Kirby\Cms\Block'] ?? null)) {
|
||||
$object = new $class($params);
|
||||
|
||||
if (is_a($object, 'Kirby\Cms\Block') === true) {
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
return new static($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the block is empty
|
||||
*
|
||||
|
@@ -167,6 +167,14 @@ class BlockConverter
|
||||
return static::editorHeading($params, 'h6');
|
||||
}
|
||||
|
||||
public static function editorHr(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [],
|
||||
'type' => 'line'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorHeading(array $params, string $level): array
|
||||
{
|
||||
return [
|
||||
|
@@ -99,6 +99,18 @@ class Blocks extends Items
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given block type exists in the collection
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public function hasType(string $type): bool
|
||||
{
|
||||
return $this->filterBy('type', $type)->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and sanitize various block formats
|
||||
*
|
||||
|
@@ -6,9 +6,9 @@ use Exception;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Form\Field;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Throwable;
|
||||
|
||||
@@ -58,6 +58,10 @@ class Blueprint
|
||||
throw new InvalidArgumentException('A blueprint model is required');
|
||||
}
|
||||
|
||||
if (is_a($props['model'], ModelWithContent::class) === false) {
|
||||
throw new InvalidArgumentException('Invalid blueprint model');
|
||||
}
|
||||
|
||||
$this->model = $props['model'];
|
||||
|
||||
// the model should not be included in the props array
|
||||
@@ -288,6 +292,8 @@ class Blueprint
|
||||
return static::$loaded[$name] = Data::read($file);
|
||||
} elseif (is_array($file) === true) {
|
||||
return static::$loaded[$name] = $file;
|
||||
} elseif (is_callable($file) === true) {
|
||||
return static::$loaded[$name] = $file($kirby);
|
||||
}
|
||||
|
||||
// neither a valid file nor array data
|
||||
@@ -697,6 +703,7 @@ class Blueprint
|
||||
'columns' => $this->normalizeColumns($tabName, $tabProps['columns'] ?? []),
|
||||
'icon' => $tabProps['icon'] ?? null,
|
||||
'label' => $this->i18n($tabProps['label'] ?? ucfirst($tabName)),
|
||||
'link' => $this->model->panel()->url(true) . '/?tab=' . $tabName,
|
||||
'name' => $tabName,
|
||||
]);
|
||||
}
|
||||
@@ -720,7 +727,13 @@ class Blueprint
|
||||
return $props;
|
||||
}
|
||||
|
||||
return static::$presets[$props['preset']]($props);
|
||||
$preset = static::$presets[$props['preset']];
|
||||
|
||||
if (is_string($preset) === true) {
|
||||
$preset = require $preset;
|
||||
}
|
||||
|
||||
return $preset($props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -760,11 +773,15 @@ class Blueprint
|
||||
/**
|
||||
* Returns a single tab by name
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function tab(string $name): ?array
|
||||
public function tab(?string $name = null): ?array
|
||||
{
|
||||
if ($name === null) {
|
||||
return A::first($this->tabs);
|
||||
}
|
||||
|
||||
return $this->tabs[$name] ?? null;
|
||||
}
|
||||
|
||||
|
@@ -165,16 +165,16 @@ class Collection extends BaseCollection
|
||||
* Checks if the given object or id
|
||||
* is in the collection
|
||||
*
|
||||
* @param string|object $id
|
||||
* @param string|object $key
|
||||
* @return bool
|
||||
*/
|
||||
public function has($id): bool
|
||||
public function has($key): bool
|
||||
{
|
||||
if (is_object($id) === true) {
|
||||
$id = $id->id();
|
||||
if (is_object($key) === true) {
|
||||
$key = $key->id();
|
||||
}
|
||||
|
||||
return parent::has($id);
|
||||
return parent::has($key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,16 +182,16 @@ class Collection extends BaseCollection
|
||||
* The method will automatically detect objects
|
||||
* or ids and then search accordingly.
|
||||
*
|
||||
* @param string|object $object
|
||||
* @param string|object $needle
|
||||
* @return int
|
||||
*/
|
||||
public function indexOf($object): int
|
||||
public function indexOf($needle): int
|
||||
{
|
||||
if (is_string($object) === true) {
|
||||
return array_search($object, $this->keys());
|
||||
if (is_string($needle) === true) {
|
||||
return array_search($needle, $this->keys());
|
||||
}
|
||||
|
||||
return array_search($object->id(), $this->keys());
|
||||
return array_search($needle->id(), $this->keys());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,17 +270,17 @@ class Collection extends BaseCollection
|
||||
* offset, limit, search and paginate on the collection.
|
||||
* Any part of the query is optional.
|
||||
*
|
||||
* @param array $query
|
||||
* @param array $arguments
|
||||
* @return static
|
||||
*/
|
||||
public function query(array $query = [])
|
||||
public function query(array $arguments = [])
|
||||
{
|
||||
$paginate = $query['paginate'] ?? null;
|
||||
$search = $query['search'] ?? null;
|
||||
$paginate = $arguments['paginate'] ?? null;
|
||||
$search = $arguments['search'] ?? null;
|
||||
|
||||
unset($query['paginate']);
|
||||
unset($arguments['paginate']);
|
||||
|
||||
$result = parent::query($query);
|
||||
$result = parent::query($arguments);
|
||||
|
||||
if (empty($search) === false) {
|
||||
if (is_array($search) === true) {
|
||||
|
@@ -3,8 +3,8 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Controller;
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
/**
|
||||
* Manages and loads all collections
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Form\Form;
|
||||
|
||||
/**
|
||||
* The Content class handles all fields
|
||||
* for content from pages, the site and users
|
||||
|
@@ -4,7 +4,7 @@ namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* Manages all content lock files
|
||||
|
550
kirby/src/Cms/Core.php
Executable file
550
kirby/src/Cms/Core.php
Executable file
@@ -0,0 +1,550 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
/**
|
||||
* The Core class lists all parts of Kirby
|
||||
* that need to be loaded or initalized in order
|
||||
* to make the system work. Most core parts can
|
||||
* be overwritten by plugins.
|
||||
*
|
||||
* You can get such lists as kirbytags, components,
|
||||
* areas, etc. by accessing them through `$kirby->core()`
|
||||
*
|
||||
* I.e. `$kirby->core()->areas()`
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Core
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $cache = [];
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\App
|
||||
*/
|
||||
protected $kirby;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
*/
|
||||
public function __construct(App $kirby)
|
||||
{
|
||||
$this->kirby = $kirby;
|
||||
$this->root = dirname(__DIR__, 2) . '/config';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the definition array of a particular area.
|
||||
*
|
||||
* This is a shortcut for `$kirby->core()->load()->area()`
|
||||
* to give faster access to original area code in plugins.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function area(string $name): ?array
|
||||
{
|
||||
return $this->load()->area($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all paths to area definition files
|
||||
*
|
||||
* They are located in `/kirby/config/areas`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function areas(): array
|
||||
{
|
||||
return [
|
||||
'account' => $this->root . '/areas/account.php',
|
||||
'installation' => $this->root . '/areas/installation.php',
|
||||
'languages' => $this->root . '/areas/languages.php',
|
||||
'login' => $this->root . '/areas/login.php',
|
||||
'site' => $this->root . '/areas/site.php',
|
||||
'system' => $this->root . '/areas/system.php',
|
||||
'users' => $this->root . '/areas/users.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all default auth challenge classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function authChallenges(): array
|
||||
{
|
||||
return [
|
||||
'email' => 'Kirby\Cms\Auth\EmailChallenge'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all paths to blueprint presets
|
||||
*
|
||||
* They are located in `/kirby/config/presets`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function blueprintPresets(): array
|
||||
{
|
||||
return [
|
||||
'pages' => $this->root . '/presets/pages.php',
|
||||
'page' => $this->root . '/presets/page.php',
|
||||
'files' => $this->root . '/presets/files.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all paths to core blueprints
|
||||
*
|
||||
* They are located in `/kirby/config/blueprints`.
|
||||
* Block blueprints are located in `/kirby/config/blocks`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function blueprints(): array
|
||||
{
|
||||
return [
|
||||
// blocks
|
||||
'blocks/code' => $this->root . '/blocks/code/code.yml',
|
||||
'blocks/gallery' => $this->root . '/blocks/gallery/gallery.yml',
|
||||
'blocks/heading' => $this->root . '/blocks/heading/heading.yml',
|
||||
'blocks/image' => $this->root . '/blocks/image/image.yml',
|
||||
'blocks/line' => $this->root . '/blocks/line/line.yml',
|
||||
'blocks/list' => $this->root . '/blocks/list/list.yml',
|
||||
'blocks/markdown' => $this->root . '/blocks/markdown/markdown.yml',
|
||||
'blocks/quote' => $this->root . '/blocks/quote/quote.yml',
|
||||
'blocks/table' => $this->root . '/blocks/table/table.yml',
|
||||
'blocks/text' => $this->root . '/blocks/text/text.yml',
|
||||
'blocks/video' => $this->root . '/blocks/video/video.yml',
|
||||
|
||||
// file blueprints
|
||||
'files/default' => $this->root . '/blueprints/files/default.yml',
|
||||
|
||||
// page blueprints
|
||||
'pages/default' => $this->root . '/blueprints/pages/default.yml',
|
||||
|
||||
// site blueprints
|
||||
'site' => $this->root . '/blueprints/site.yml'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all cache driver classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function cacheTypes(): array
|
||||
{
|
||||
return [
|
||||
'apcu' => 'Kirby\Cache\ApcuCache',
|
||||
'file' => 'Kirby\Cache\FileCache',
|
||||
'memcached' => 'Kirby\Cache\MemCached',
|
||||
'memory' => 'Kirby\Cache\MemoryCache',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all core component functions
|
||||
*
|
||||
* The component functions can be found in
|
||||
* `/kirby/config/components.php`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function components(): array
|
||||
{
|
||||
return $this->cache['components'] ?? $this->cache['components'] = include $this->root . '/components.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all field method aliases
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fieldMethodAliases(): array
|
||||
{
|
||||
return [
|
||||
'bool' => 'toBool',
|
||||
'esc' => 'escape',
|
||||
'excerpt' => 'toExcerpt',
|
||||
'float' => 'toFloat',
|
||||
'h' => 'html',
|
||||
'int' => 'toInt',
|
||||
'kt' => 'kirbytext',
|
||||
'kti' => 'kirbytextinline',
|
||||
'link' => 'toLink',
|
||||
'md' => 'markdown',
|
||||
'sp' => 'smartypants',
|
||||
'v' => 'isValid',
|
||||
'x' => 'xml'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all field method functions
|
||||
*
|
||||
* Field methods are stored in `/kirby/config/methods.php`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fieldMethods(): array
|
||||
{
|
||||
return $this->cache['fieldMethods'] ?? $this->cache['fieldMethods'] = (include $this->root . '/methods.php')($this->kirby);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of paths for field mixins
|
||||
*
|
||||
* They are located in `/kirby/config/fields/mixins`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fieldMixins(): array
|
||||
{
|
||||
return [
|
||||
'datetime' => $this->root . '/fields/mixins/datetime.php',
|
||||
'filepicker' => $this->root . '/fields/mixins/filepicker.php',
|
||||
'layout' => $this->root . '/fields/mixins/layout.php',
|
||||
'min' => $this->root . '/fields/mixins/min.php',
|
||||
'options' => $this->root . '/fields/mixins/options.php',
|
||||
'pagepicker' => $this->root . '/fields/mixins/pagepicker.php',
|
||||
'picker' => $this->root . '/fields/mixins/picker.php',
|
||||
'upload' => $this->root . '/fields/mixins/upload.php',
|
||||
'userpicker' => $this->root . '/fields/mixins/userpicker.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all paths and class names of panel fields
|
||||
*
|
||||
* Traditional panel fields are located in `/kirby/config/fields`
|
||||
*
|
||||
* The more complex field classes can be found in
|
||||
* `/kirby/src/Form/Fields`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
'blocks' => 'Kirby\Form\Field\BlocksField',
|
||||
'checkboxes' => $this->root . '/fields/checkboxes.php',
|
||||
'date' => $this->root . '/fields/date.php',
|
||||
'email' => $this->root . '/fields/email.php',
|
||||
'files' => $this->root . '/fields/files.php',
|
||||
'gap' => $this->root . '/fields/gap.php',
|
||||
'headline' => $this->root . '/fields/headline.php',
|
||||
'hidden' => $this->root . '/fields/hidden.php',
|
||||
'info' => $this->root . '/fields/info.php',
|
||||
'layout' => 'Kirby\Form\Field\LayoutField',
|
||||
'line' => $this->root . '/fields/line.php',
|
||||
'list' => $this->root . '/fields/list.php',
|
||||
'multiselect' => $this->root . '/fields/multiselect.php',
|
||||
'number' => $this->root . '/fields/number.php',
|
||||
'pages' => $this->root . '/fields/pages.php',
|
||||
'radio' => $this->root . '/fields/radio.php',
|
||||
'range' => $this->root . '/fields/range.php',
|
||||
'select' => $this->root . '/fields/select.php',
|
||||
'slug' => $this->root . '/fields/slug.php',
|
||||
'structure' => $this->root . '/fields/structure.php',
|
||||
'tags' => $this->root . '/fields/tags.php',
|
||||
'tel' => $this->root . '/fields/tel.php',
|
||||
'text' => $this->root . '/fields/text.php',
|
||||
'textarea' => $this->root . '/fields/textarea.php',
|
||||
'time' => $this->root . '/fields/time.php',
|
||||
'toggle' => $this->root . '/fields/toggle.php',
|
||||
'url' => $this->root . '/fields/url.php',
|
||||
'users' => $this->root . '/fields/users.php',
|
||||
'writer' => $this->root . '/fields/writer.php'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all kirbytag aliases
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function kirbyTagAliases(): array
|
||||
{
|
||||
return [
|
||||
'youtube' => 'video',
|
||||
'vimeo' => 'video'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all kirbytag definitions
|
||||
*
|
||||
* They are located in `/kirby/config/tags.php`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function kirbyTags(): array
|
||||
{
|
||||
return $this->cache['kirbytags'] ?? $this->cache['kirbytags'] = include $this->root . '/tags.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a core part of Kirby
|
||||
*
|
||||
* The loader is set to not include plugins.
|
||||
* This way, you can access original Kirby core code
|
||||
* through this load method.
|
||||
*
|
||||
* @return \Kirby\Cms\Loader
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
return new Loader($this->kirby, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all absolute paths to important directories
|
||||
*
|
||||
* Roots are resolved and baked in `\Kirby\Cms\App::bakeRoots()`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function roots(): array
|
||||
{
|
||||
return $this->cache['roots'] ?? $this->cache['roots'] = [
|
||||
// kirby
|
||||
'kirby' => function (array $roots) {
|
||||
return dirname(__DIR__, 2);
|
||||
},
|
||||
|
||||
// i18n
|
||||
'i18n' => function (array $roots) {
|
||||
return $roots['kirby'] . '/i18n';
|
||||
},
|
||||
'i18n:translations' => function (array $roots) {
|
||||
return $roots['i18n'] . '/translations';
|
||||
},
|
||||
'i18n:rules' => function (array $roots) {
|
||||
return $roots['i18n'] . '/rules';
|
||||
},
|
||||
|
||||
// index
|
||||
'index' => function (array $roots) {
|
||||
return dirname(__DIR__, 3);
|
||||
},
|
||||
|
||||
// assets
|
||||
'assets' => function (array $roots) {
|
||||
return $roots['index'] . '/assets';
|
||||
},
|
||||
|
||||
// content
|
||||
'content' => function (array $roots) {
|
||||
return $roots['index'] . '/content';
|
||||
},
|
||||
|
||||
// media
|
||||
'media' => function (array $roots) {
|
||||
return $roots['index'] . '/media';
|
||||
},
|
||||
|
||||
// panel
|
||||
'panel' => function (array $roots) {
|
||||
return $roots['kirby'] . '/panel';
|
||||
},
|
||||
|
||||
// site
|
||||
'site' => function (array $roots) {
|
||||
return $roots['index'] . '/site';
|
||||
},
|
||||
'accounts' => function (array $roots) {
|
||||
return $roots['site'] . '/accounts';
|
||||
},
|
||||
'blueprints' => function (array $roots) {
|
||||
return $roots['site'] . '/blueprints';
|
||||
},
|
||||
'cache' => function (array $roots) {
|
||||
return $roots['site'] . '/cache';
|
||||
},
|
||||
'collections' => function (array $roots) {
|
||||
return $roots['site'] . '/collections';
|
||||
},
|
||||
'config' => function (array $roots) {
|
||||
return $roots['site'] . '/config';
|
||||
},
|
||||
'controllers' => function (array $roots) {
|
||||
return $roots['site'] . '/controllers';
|
||||
},
|
||||
'languages' => function (array $roots) {
|
||||
return $roots['site'] . '/languages';
|
||||
},
|
||||
'license' => function (array $roots) {
|
||||
return $roots['config'] . '/.license';
|
||||
},
|
||||
'logs' => function (array $roots) {
|
||||
return $roots['site'] . '/logs';
|
||||
},
|
||||
'models' => function (array $roots) {
|
||||
return $roots['site'] . '/models';
|
||||
},
|
||||
'plugins' => function (array $roots) {
|
||||
return $roots['site'] . '/plugins';
|
||||
},
|
||||
'sessions' => function (array $roots) {
|
||||
return $roots['site'] . '/sessions';
|
||||
},
|
||||
'snippets' => function (array $roots) {
|
||||
return $roots['site'] . '/snippets';
|
||||
},
|
||||
'templates' => function (array $roots) {
|
||||
return $roots['site'] . '/templates';
|
||||
},
|
||||
|
||||
// blueprints
|
||||
'roles' => function (array $roots) {
|
||||
return $roots['blueprints'] . '/users';
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all routes for Kirby’s router
|
||||
*
|
||||
* Routes are split into `before` and `after` routes.
|
||||
*
|
||||
* Plugin routes will be injected inbetween.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function routes(): array
|
||||
{
|
||||
return $this->cache['routes'] ?? $this->cache['routes'] = (include $this->root . '/routes.php')($this->kirby);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all paths to core block snippets
|
||||
*
|
||||
* They are located in `/kirby/config/blocks`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function snippets(): array
|
||||
{
|
||||
return [
|
||||
'blocks/code' => $this->root . '/blocks/code/code.php',
|
||||
'blocks/gallery' => $this->root . '/blocks/gallery/gallery.php',
|
||||
'blocks/heading' => $this->root . '/blocks/heading/heading.php',
|
||||
'blocks/image' => $this->root . '/blocks/image/image.php',
|
||||
'blocks/line' => $this->root . '/blocks/line/line.php',
|
||||
'blocks/list' => $this->root . '/blocks/list/list.php',
|
||||
'blocks/markdown' => $this->root . '/blocks/markdown/markdown.php',
|
||||
'blocks/quote' => $this->root . '/blocks/quote/quote.php',
|
||||
'blocks/table' => $this->root . '/blocks/table/table.php',
|
||||
'blocks/text' => $this->root . '/blocks/text/text.php',
|
||||
'blocks/video' => $this->root . '/blocks/video/video.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of paths to section mixins
|
||||
*
|
||||
* They are located in `/kirby/config/sections/mixins`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sectionMixins(): array
|
||||
{
|
||||
return [
|
||||
'empty' => $this->root . '/sections/mixins/empty.php',
|
||||
'headline' => $this->root . '/sections/mixins/headline.php',
|
||||
'help' => $this->root . '/sections/mixins/help.php',
|
||||
'layout' => $this->root . '/sections/mixins/layout.php',
|
||||
'max' => $this->root . '/sections/mixins/max.php',
|
||||
'min' => $this->root . '/sections/mixins/min.php',
|
||||
'pagination' => $this->root . '/sections/mixins/pagination.php',
|
||||
'parent' => $this->root . '/sections/mixins/parent.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all section definitions
|
||||
*
|
||||
* They are located in `/kirby/config/sections`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sections(): array
|
||||
{
|
||||
return [
|
||||
'fields' => $this->root . '/sections/fields.php',
|
||||
'files' => $this->root . '/sections/files.php',
|
||||
'info' => $this->root . '/sections/info.php',
|
||||
'pages' => $this->root . '/sections/pages.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of paths to all system templates
|
||||
*
|
||||
* They are located in `/kirby/config/templates`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function templates(): array
|
||||
{
|
||||
return [
|
||||
'emails/auth/login' => $this->root . '/templates/emails/auth/login.php',
|
||||
'emails/auth/password-reset' => $this->root . '/templates/emails/auth/password-reset.php'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all system URLs
|
||||
*
|
||||
* URLs are resolved and baked in `\Kirby\Cms\App::bakeUrls()`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function urls(): array
|
||||
{
|
||||
return $this->cache['urls'] ?? $this->cache['urls'] = [
|
||||
'index' => function () {
|
||||
return Url::index();
|
||||
},
|
||||
'base' => function (array $urls) {
|
||||
return rtrim($urls['index'], '/');
|
||||
},
|
||||
'current' => function (array $urls) {
|
||||
$path = trim($this->kirby->path(), '/');
|
||||
|
||||
if (empty($path) === true) {
|
||||
return $urls['index'];
|
||||
} else {
|
||||
return $urls['base'] . '/' . $path;
|
||||
}
|
||||
},
|
||||
'assets' => function (array $urls) {
|
||||
return $urls['base'] . '/assets';
|
||||
},
|
||||
'api' => function (array $urls) {
|
||||
return $urls['base'] . '/' . $this->kirby->option('api.slug', 'api');
|
||||
},
|
||||
'media' => function (array $urls) {
|
||||
return $urls['base'] . '/media';
|
||||
},
|
||||
'panel' => function (array $urls) {
|
||||
return $urls['base'] . '/' . $this->kirby->option('panel.slug', 'panel');
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
@@ -1,180 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
/**
|
||||
* Extension of the Toolkit `Dir` class with a new
|
||||
* `Dir::inventory` method, that handles scanning directories
|
||||
* and converts the results into our children, files and
|
||||
* other page stuff.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Dir extends \Kirby\Toolkit\Dir
|
||||
{
|
||||
public static $numSeparator = '_';
|
||||
|
||||
/**
|
||||
* Scans the directory and analyzes files,
|
||||
* content, meta info and children. This is used
|
||||
* in Page, Site and User objects to fetch all
|
||||
* relevant information.
|
||||
*
|
||||
* @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 $contentIgnore = null, bool $multilang = false): array
|
||||
{
|
||||
$dir = realpath($dir);
|
||||
|
||||
$inventory = [
|
||||
'children' => [],
|
||||
'files' => [],
|
||||
'template' => 'default',
|
||||
];
|
||||
|
||||
if ($dir === false) {
|
||||
return $inventory;
|
||||
}
|
||||
|
||||
$items = Dir::read($dir, $contentIgnore);
|
||||
|
||||
// a temporary store for all content files
|
||||
$content = [];
|
||||
|
||||
// sort all items naturally to avoid sorting issues later
|
||||
natsort($items);
|
||||
|
||||
foreach ($items as $item) {
|
||||
|
||||
// ignore all items with a leading dot
|
||||
if (in_array(substr($item, 0, 1), ['.', '_']) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$root = $dir . '/' . $item;
|
||||
|
||||
if (is_dir($root) === true) {
|
||||
|
||||
// extract the slug and num of the directory
|
||||
if (preg_match('/^([0-9]+)' . static::$numSeparator . '(.*)$/', $item, $match)) {
|
||||
$num = $match[1];
|
||||
$slug = $match[2];
|
||||
} else {
|
||||
$num = null;
|
||||
$slug = $item;
|
||||
}
|
||||
|
||||
$inventory['children'][] = [
|
||||
'dirname' => $item,
|
||||
'model' => null,
|
||||
'num' => $num,
|
||||
'root' => $root,
|
||||
'slug' => $slug,
|
||||
];
|
||||
} else {
|
||||
$extension = pathinfo($item, PATHINFO_EXTENSION);
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove the language codes from all content filenames
|
||||
if ($multilang === true) {
|
||||
foreach ($content as $key => $filename) {
|
||||
$content[$key] = pathinfo($filename, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
$content = array_unique($content);
|
||||
}
|
||||
|
||||
$inventory = static::inventoryContent($inventory, $content);
|
||||
$inventory = static::inventoryModels($inventory, $contentExtension, $multilang);
|
||||
|
||||
return $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 inventoryContent(array $inventory, array $content): array
|
||||
{
|
||||
|
||||
// filter meta files from the content file
|
||||
if (empty($content) === true) {
|
||||
$inventory['template'] = 'default';
|
||||
return $inventory;
|
||||
}
|
||||
|
||||
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
|
||||
$inventory['template'] = $contentName;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -75,14 +75,14 @@ class Field
|
||||
$method = strtolower($method);
|
||||
|
||||
if (isset(static::$methods[$method]) === true) {
|
||||
return static::$methods[$method](clone $this, ...$arguments);
|
||||
return (static::$methods[$method])(clone $this, ...$arguments);
|
||||
}
|
||||
|
||||
if (isset(static::$aliases[$method]) === true) {
|
||||
$method = strtolower(static::$aliases[$method]);
|
||||
|
||||
if (isset(static::$methods[$method]) === true) {
|
||||
return static::$methods[$method](clone $this, ...$arguments);
|
||||
return (static::$methods[$method])(clone $this, ...$arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
@@ -21,6 +22,7 @@ class Fieldset extends Item
|
||||
const ITEMS_CLASS = '\Kirby\Cms\Fieldsets';
|
||||
|
||||
protected $disabled;
|
||||
protected $editable;
|
||||
protected $fields = [];
|
||||
protected $icon;
|
||||
protected $label;
|
||||
@@ -49,6 +51,7 @@ class Fieldset extends Item
|
||||
parent::__construct($params);
|
||||
|
||||
$this->disabled = $params['disabled'] ?? false;
|
||||
$this->editable = $params['editable'] ?? true;
|
||||
$this->icon = $params['icon'] ?? null;
|
||||
$this->model = $this->parent;
|
||||
$this->name = $this->createName($params['name'] ?? Str::ucfirst($this->type));
|
||||
@@ -70,6 +73,10 @@ class Fieldset extends Item
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
* @return array
|
||||
*/
|
||||
protected function createFields(array $fields = []): array
|
||||
{
|
||||
$fields = Blueprint::fieldsProps($fields);
|
||||
@@ -81,16 +88,28 @@ class Fieldset extends Item
|
||||
return $fields;
|
||||
}
|
||||
|
||||
protected function createName($name): string
|
||||
/**
|
||||
* @param array|string $name
|
||||
* @return string|null
|
||||
*/
|
||||
protected function createName($name): ?string
|
||||
{
|
||||
return I18n::translate($name, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $label
|
||||
* @return string|null
|
||||
*/
|
||||
protected function createLabel($label = null): ?string
|
||||
{
|
||||
return I18n::translate($label, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function createTabs(array $params = []): array
|
||||
{
|
||||
$tabs = $params['tabs'] ?? [];
|
||||
@@ -124,11 +143,33 @@ class Fieldset extends Item
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function disabled(): bool
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function editable(): bool
|
||||
{
|
||||
if ($this->editable === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($this->fields) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return $this->fields;
|
||||
@@ -139,7 +180,7 @@ class Fieldset extends Item
|
||||
*
|
||||
* @param array $fields
|
||||
* @param array $input
|
||||
* @return \Kirby\Cms\Form
|
||||
* @return \Kirby\Form\Form
|
||||
*/
|
||||
public function form(array $fields, array $input = [])
|
||||
{
|
||||
@@ -151,36 +192,65 @@ class Fieldset extends Item
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function icon(): ?string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function label(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Kirby\Cms\ModelWithContent
|
||||
*/
|
||||
public function model()
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|bool
|
||||
*/
|
||||
public function preview()
|
||||
{
|
||||
return $this->preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function tabs(): array
|
||||
{
|
||||
return $this->tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function translate(): bool
|
||||
{
|
||||
return $this->translate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
return $this->type;
|
||||
@@ -192,21 +262,33 @@ class Fieldset extends Item
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'disabled' => $this->disabled,
|
||||
'icon' => $this->icon,
|
||||
'label' => $this->label,
|
||||
'name' => $this->name,
|
||||
'preview' => $this->preview,
|
||||
'tabs' => $this->tabs,
|
||||
'translate' => $this->translate,
|
||||
'type' => $this->type,
|
||||
'unset' => $this->unset,
|
||||
'wysiwyg' => $this->wysiwyg,
|
||||
'disabled' => $this->disabled(),
|
||||
'editable' => $this->editable(),
|
||||
'icon' => $this->icon(),
|
||||
'label' => $this->label(),
|
||||
'name' => $this->name(),
|
||||
'preview' => $this->preview(),
|
||||
'tabs' => $this->tabs(),
|
||||
'translate' => $this->translate(),
|
||||
'type' => $this->type(),
|
||||
'unset' => $this->unset(),
|
||||
'wysiwyg' => $this->wysiwyg(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function unset(): bool
|
||||
{
|
||||
return $this->unset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function wysiwyg(): bool
|
||||
{
|
||||
return $this->wysiwyg;
|
||||
}
|
||||
}
|
||||
|
@@ -74,6 +74,7 @@ class Fieldsets extends Items
|
||||
'gallery' => 'blocks/gallery',
|
||||
'heading' => 'blocks/heading',
|
||||
'image' => 'blocks/image',
|
||||
'line' => 'blocks/line',
|
||||
'list' => 'blocks/list',
|
||||
'markdown' => 'blocks/markdown',
|
||||
'quote' => 'blocks/quote',
|
||||
|
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Image\Image;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Filesystem\IsFile;
|
||||
use Kirby\Panel\File as Panel;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\F;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* The `$file` object provides a set
|
||||
@@ -16,11 +15,11 @@ use Throwable;
|
||||
* URL or resizing an image. It also
|
||||
* handles file meta data.
|
||||
*
|
||||
* The File class is a wrapper around
|
||||
* the Kirby\Image\Image class, which
|
||||
* is used to handle all file methods.
|
||||
* The File class proxies the `Kirby\Filesystem\File`
|
||||
* or `Kirby\Image\Image` class, which
|
||||
* is used to handle all asset file methods.
|
||||
* In addition the File class handles
|
||||
* File meta data via Kirby\Cms\Content.
|
||||
* meta data via `Kirby\Cms\Content`.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
@@ -33,19 +32,10 @@ class File extends ModelWithContent
|
||||
const CLASS_ALIAS = 'file';
|
||||
|
||||
use FileActions;
|
||||
use FileFoundation;
|
||||
use FileModifications;
|
||||
use HasMethods;
|
||||
use HasSiblings;
|
||||
|
||||
/**
|
||||
* The parent asset object
|
||||
* This is used to do actual file
|
||||
* method calls, like size, mime, etc.
|
||||
*
|
||||
* @var \Kirby\Image\Image
|
||||
*/
|
||||
protected $asset;
|
||||
use IsFile;
|
||||
|
||||
/**
|
||||
* Cache for the initialized blueprint object
|
||||
@@ -57,12 +47,12 @@ class File extends ModelWithContent
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filename;
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* All registered file methods
|
||||
@@ -123,7 +113,7 @@ class File extends ModelWithContent
|
||||
}
|
||||
|
||||
// content fields
|
||||
return $this->content()->get($method, $arguments);
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +123,11 @@ class File extends ModelWithContent
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
// properties
|
||||
// set filename as the most important prop first
|
||||
// TODO: refactor later to avoid redundant prop setting
|
||||
$this->setProperty('filename', $props['filename'] ?? null, true);
|
||||
|
||||
// set other properties
|
||||
$this->setProperties($props);
|
||||
}
|
||||
|
||||
@@ -162,17 +156,6 @@ class File extends ModelWithContent
|
||||
return $this->parent()->apiUrl($relative) . '/files/' . $this->filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Image object
|
||||
*
|
||||
* @internal
|
||||
* @return \Kirby\Image\Image
|
||||
*/
|
||||
public function asset()
|
||||
{
|
||||
return $this->asset = $this->asset ?? new Image($this->root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FileBlueprint object for the file
|
||||
*
|
||||
@@ -226,43 +209,6 @@ class File extends ModelWithContent
|
||||
return $this->filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the file, which will be
|
||||
* used in the panel, when the file
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (null|auto|kirbytext|markdown)
|
||||
* @param bool $absolute
|
||||
* @return string
|
||||
*/
|
||||
public function dragText(string $type = null, bool $absolute = false): string
|
||||
{
|
||||
$type = $this->dragTextType($type);
|
||||
$url = $absolute ? $this->id() : $this->filename();
|
||||
|
||||
if ($dragTextFromCallback = $this->dragTextFromCallback($type, $url)) {
|
||||
return $dragTextFromCallback;
|
||||
}
|
||||
|
||||
if ($type === 'markdown') {
|
||||
if ($this->type() === 'image') {
|
||||
return '';
|
||||
} else {
|
||||
return '[' . $this->filename() . '](' . $url . ')';
|
||||
}
|
||||
} else {
|
||||
if ($this->type() === 'image') {
|
||||
return '(image: ' . $url . ')';
|
||||
} elseif ($this->type() === 'video') {
|
||||
return '(video: ' . $url . ')';
|
||||
} else {
|
||||
return '(file: ' . $url . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a File object
|
||||
*
|
||||
@@ -295,6 +241,20 @@ class File extends ModelWithContent
|
||||
return $this->siblingsCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file to html
|
||||
*
|
||||
* @param array $attr
|
||||
* @return string
|
||||
*/
|
||||
public function html(array $attr = []): string
|
||||
{
|
||||
return $this->asset()->html(array_merge(
|
||||
['alt' => $this->alt()],
|
||||
$attr
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id
|
||||
*
|
||||
@@ -446,156 +406,13 @@ class File extends ModelWithContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel icon definition
|
||||
* Returns the panel info object
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
* @return \Kirby\Panel\File
|
||||
*/
|
||||
public function panelIcon(array $params = null): array
|
||||
public function panel()
|
||||
{
|
||||
$colorBlue = '#81a2be';
|
||||
$colorPurple = '#b294bb';
|
||||
$colorOrange = '#de935f';
|
||||
$colorGreen = '#a7bd68';
|
||||
$colorAqua = '#8abeb7';
|
||||
$colorYellow = '#f0c674';
|
||||
$colorRed = '#d16464';
|
||||
$colorWhite = '#c5c9c6';
|
||||
|
||||
$types = [
|
||||
'image' => ['color' => $colorOrange, 'type' => 'file-image'],
|
||||
'video' => ['color' => $colorYellow, 'type' => 'file-video'],
|
||||
'document' => ['color' => $colorRed, 'type' => 'file-document'],
|
||||
'audio' => ['color' => $colorAqua, 'type' => 'file-audio'],
|
||||
'code' => ['color' => $colorBlue, 'type' => 'file-code'],
|
||||
'archive' => ['color' => $colorWhite, 'type' => 'file-zip'],
|
||||
];
|
||||
|
||||
$extensions = [
|
||||
'indd' => ['color' => $colorPurple],
|
||||
'xls' => ['color' => $colorGreen, 'type' => 'file-spreadsheet'],
|
||||
'xlsx' => ['color' => $colorGreen, 'type' => 'file-spreadsheet'],
|
||||
'csv' => ['color' => $colorGreen, 'type' => 'file-spreadsheet'],
|
||||
'docx' => ['color' => $colorBlue, 'type' => 'file-word'],
|
||||
'doc' => ['color' => $colorBlue, 'type' => 'file-word'],
|
||||
'rtf' => ['color' => $colorBlue, 'type' => 'file-word'],
|
||||
'mdown' => ['type' => 'file-text'],
|
||||
'md' => ['type' => 'file-text']
|
||||
];
|
||||
|
||||
$definition = array_merge($types[$this->type()] ?? [], $extensions[$this->extension()] ?? []);
|
||||
|
||||
$params['type'] = $definition['type'] ?? 'file';
|
||||
$params['color'] = $definition['color'] ?? $colorWhite;
|
||||
|
||||
return parent::panelIcon($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Cms\Asset|null
|
||||
*/
|
||||
protected function panelImageSource(string $query = null)
|
||||
{
|
||||
if ($query === null && $this->isViewable()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return parent::panelImageSource($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
*
|
||||
* @since 3.3.0 This also checks for the lock status
|
||||
* @since 3.5.1 This also checks for matching accept settings
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
*/
|
||||
public function panelOptions(array $unlock = []): array
|
||||
{
|
||||
$options = parent::panelOptions($unlock);
|
||||
|
||||
try {
|
||||
// check if the file type is allowed at all,
|
||||
// otherwise it cannot be replaced
|
||||
$this->match($this->blueprint()->accept());
|
||||
} catch (Throwable $e) {
|
||||
$options['replace'] = false;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function panelPath(): string
|
||||
{
|
||||
return 'files/' . $this->filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the response data for file pickers
|
||||
* and file fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function panelPickerData(array $params = []): array
|
||||
{
|
||||
$image = $this->panelImage($params['image'] ?? []);
|
||||
$icon = $this->panelIcon($image);
|
||||
$uuid = $this->id();
|
||||
|
||||
if (empty($params['model']) === false) {
|
||||
$uuid = $this->parent() === $params['model'] ? $this->filename() : $this->id();
|
||||
$absolute = $this->parent() !== $params['model'];
|
||||
}
|
||||
|
||||
// escape the default text
|
||||
// TODO: no longer needed in 3.6
|
||||
$textQuery = $params['text'] ?? '{{ file.filename }}';
|
||||
$text = $this->toString($textQuery);
|
||||
if ($textQuery === '{{ file.filename }}') {
|
||||
$text = Escape::html($text);
|
||||
}
|
||||
|
||||
return [
|
||||
'filename' => $this->filename(),
|
||||
'dragText' => $this->dragText('auto', $absolute ?? false),
|
||||
'icon' => $icon,
|
||||
'id' => $this->id(),
|
||||
'image' => $image,
|
||||
'info' => $this->toString($params['info'] ?? false),
|
||||
'link' => $this->panelUrl(true),
|
||||
'text' => $text,
|
||||
'type' => $this->type(),
|
||||
'url' => $this->url(),
|
||||
'uuid' => $uuid,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
return $this->parent()->panelUrl($relative) . '/' . $this->panelPath();
|
||||
return new Panel($this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -612,6 +429,7 @@ class File extends ModelWithContent
|
||||
* Returns the parent id if a parent exists
|
||||
*
|
||||
* @internal
|
||||
* @todo 3.7.0 When setParent() is changed, the if check is not needed anymore
|
||||
* @return string|null
|
||||
*/
|
||||
public function parentId(): ?string
|
||||
@@ -697,13 +515,22 @@ class File extends ModelWithContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent model object
|
||||
* Sets the parent model object;
|
||||
* this property is required for `File::create()` and
|
||||
* will be generally required starting with Kirby 3.7.0
|
||||
*
|
||||
* @param \Kirby\Cms\Model|null $parent
|
||||
* @return $this
|
||||
* @todo make property required in 3.7.0
|
||||
*/
|
||||
protected function setParent(Model $parent = null)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($parent === null) {
|
||||
deprecated('You are creating a `Kirby\Cms\File` object without passing the `parent` property. While unsupported, this hasn\'t caused any direct errors so far. To fix inconsistencies, the `parent` property will be required when creating a `Kirby\Cms\File` object in Kirby 3.7.0 and higher. Not passing this property will start throwing a breaking error.');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->parent = $parent;
|
||||
return $this;
|
||||
}
|
||||
@@ -806,4 +633,134 @@ class File extends ModelWithContent
|
||||
{
|
||||
return $this->url ?? $this->url = ($this->kirby()->component('file::url'))($this->kirby(), $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deprecated!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the file, which will be
|
||||
* used in the panel, when the file
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (null|auto|kirbytext|markdown)
|
||||
* @param bool $absolute
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function dragText(string $type = null, bool $absolute = false): string
|
||||
{
|
||||
return $this->panel()->dragText($type, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @since 3.3.0 This also checks for the lock status
|
||||
* @since 3.5.1 This also checks for matching accept settings
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelOptions(array $unlock = []): array
|
||||
{
|
||||
return $this->panel()->options($unlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelPath(): string
|
||||
{
|
||||
return $this->panel()->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the response data for file pickers
|
||||
* and file fields
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelPickerData(array $params = []): array
|
||||
{
|
||||
return $this->panel()->pickerData($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
return $this->panel()->url($relative);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified File URL that uses the parent
|
||||
* Page URL and the filename as a more stable
|
||||
* alternative for the media URLs.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function previewUrl(): string
|
||||
{
|
||||
$parent = $this->parent();
|
||||
$url = url($this->id());
|
||||
|
||||
switch ($parent::CLASS_ALIAS) {
|
||||
case 'page':
|
||||
$preview = $parent->blueprint()->preview();
|
||||
|
||||
// the page has a custom preview setting,
|
||||
// thus the file is only accessible through
|
||||
// the direct media URL
|
||||
if ($preview !== true) {
|
||||
return $this->url();
|
||||
}
|
||||
|
||||
// it's more stable to access files for drafts
|
||||
// through their direct URL to avoid conflicts
|
||||
// with draft token verification
|
||||
if ($parent->isDraft() === true) {
|
||||
return $this->url();
|
||||
}
|
||||
|
||||
return $url;
|
||||
case 'user':
|
||||
return $this->url();
|
||||
default:
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,8 +5,8 @@ namespace Kirby\Cms;
|
||||
use Closure;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Image\Image;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Form\Form;
|
||||
|
||||
/**
|
||||
* FileActions
|
||||
@@ -75,6 +75,8 @@ trait FileActions
|
||||
F::move($oldFile->contentFile(), $newFile->contentFile());
|
||||
}
|
||||
|
||||
$newFile->parent()->files()->remove($oldFile->id());
|
||||
$newFile->parent()->files()->set($newFile->id(), $newFile);
|
||||
|
||||
return $newFile;
|
||||
});
|
||||
@@ -178,7 +180,7 @@ trait FileActions
|
||||
|
||||
// create the basic file and a test upload object
|
||||
$file = static::factory($props);
|
||||
$upload = new Image($props['source']);
|
||||
$upload = $file->asset($props['source']);
|
||||
|
||||
// create a form for the file
|
||||
$form = Form::for($file, [
|
||||
@@ -277,7 +279,14 @@ trait FileActions
|
||||
*/
|
||||
public function replace(string $source)
|
||||
{
|
||||
return $this->commit('replace', ['file' => $this, 'upload' => new Image($source)], function ($file, $upload) {
|
||||
$file = $this->clone();
|
||||
|
||||
$arguments = [
|
||||
'file' => $file,
|
||||
'upload' => $file->asset($source)
|
||||
];
|
||||
|
||||
return $this->commit('replace', $arguments, function ($file, $upload) {
|
||||
|
||||
// delete all public versions
|
||||
$file->unpublish();
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -81,7 +81,7 @@ class FileBlueprint extends Blueprint
|
||||
|
||||
if (is_array($accept['extension']) === true) {
|
||||
// determine the main MIME type for each extension
|
||||
$restrictions[] = array_map(['Kirby\Toolkit\Mime', 'fromExtension'], $accept['extension']);
|
||||
$restrictions[] = array_map(['Kirby\Filesystem\Mime', 'fromExtension'], $accept['extension']);
|
||||
}
|
||||
|
||||
if (is_array($accept['type']) === true) {
|
||||
@@ -89,7 +89,7 @@ class FileBlueprint extends Blueprint
|
||||
$mimes = [];
|
||||
foreach ($accept['type'] as $type) {
|
||||
if ($extensions = F::typeToExtensions($type)) {
|
||||
$mimes[] = array_map(['Kirby\Toolkit\Mime', 'fromExtension'], $extensions);
|
||||
$mimes[] = array_map(['Kirby\Filesystem\Mime', 'fromExtension'], $extensions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,248 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\BadMethodCallException;
|
||||
use Kirby\Image\Image;
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
/**
|
||||
* Foundation for all file objects
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
trait FileFoundation
|
||||
{
|
||||
protected $asset;
|
||||
protected $root;
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* Magic caller for asset methods
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
* @throws \Kirby\Exception\BadMethodCallException
|
||||
*/
|
||||
public function __call(string $method, array $arguments = [])
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
throw new BadMethodCallException('The method: "' . $method . '" does not exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor sets all file properties
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->setProperties($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file object to a string
|
||||
* In case of an image, it will create an image tag
|
||||
* Otherwise it will return the url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->type() === 'image') {
|
||||
return $this->html();
|
||||
}
|
||||
|
||||
return $this->url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Image object
|
||||
*
|
||||
* @return \Kirby\Image\Image
|
||||
*/
|
||||
public function asset()
|
||||
{
|
||||
return $this->asset = $this->asset ?? new Image($this->root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file exists on disk
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return file_exists($this->root()) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function extension(): string
|
||||
{
|
||||
return F::extension($this->root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file to html
|
||||
*
|
||||
* @param array $attr
|
||||
* @return string
|
||||
*/
|
||||
public function html(array $attr = []): string
|
||||
{
|
||||
if ($this->type() === 'image') {
|
||||
return Html::img($this->url(), array_merge(['alt' => $this->alt()], $attr));
|
||||
} else {
|
||||
return Html::a($this->url(), $attr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file is a resizable image
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isResizable(): bool
|
||||
{
|
||||
$resizable = [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'gif',
|
||||
'png',
|
||||
'webp'
|
||||
];
|
||||
|
||||
return in_array($this->extension(), $resizable) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a preview can be displayed for the file
|
||||
* in the panel or in the frontend
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isViewable(): bool
|
||||
{
|
||||
$viewable = [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'gif',
|
||||
'png',
|
||||
'svg',
|
||||
'webp'
|
||||
];
|
||||
|
||||
return in_array($this->extension(), $viewable) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app instance
|
||||
*
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
public function kirby()
|
||||
{
|
||||
return App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's last modification time.
|
||||
*
|
||||
* @param string $format
|
||||
* @param string|null $handler date or strftime
|
||||
* @return mixed
|
||||
*/
|
||||
public function modified(string $format = null, string $handler = null)
|
||||
{
|
||||
return F::modified($this->root(), $format, $handler ?? $this->kirby()->option('date.handler', 'date'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the file root
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function root(): ?string
|
||||
{
|
||||
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 $url
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUrl(string $url)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = array_merge($this->asset()->toArray(), [
|
||||
'isResizable' => $this->isResizable(),
|
||||
'url' => $this->url(),
|
||||
]);
|
||||
|
||||
ksort($array);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file type
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function type(): ?string
|
||||
{
|
||||
return F::type($this->root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute url for the file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ namespace Kirby\Cms;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Resizing, blurring etc.
|
||||
* Trait for image resizing, blurring etc.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
@@ -191,9 +191,20 @@ trait FileModifications
|
||||
return $this;
|
||||
}
|
||||
|
||||
$result = ($this->kirby()->component('file::version'))($this->kirby(), $this, $options);
|
||||
// fallback to global config options
|
||||
if (isset($options['format']) === false) {
|
||||
if ($format = $this->kirby()->option('thumbs.format')) {
|
||||
$options['format'] = $format;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_a($result, 'Kirby\Cms\FileVersion') === false && is_a($result, 'Kirby\Cms\File') === false) {
|
||||
$component = $this->kirby()->component('file::version');
|
||||
$result = $component($this->kirby(), $this, $options);
|
||||
|
||||
if (
|
||||
is_a($result, 'Kirby\Cms\FileVersion') === false &&
|
||||
is_a($result, 'Kirby\Cms\File') === false
|
||||
) {
|
||||
throw new InvalidArgumentException('The file::version component must return a File or FileVersion object');
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ namespace Kirby\Cms;
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Image\Image;
|
||||
use Kirby\Filesystem\File as BaseFile;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
@@ -38,6 +38,12 @@ class FileRules
|
||||
]);
|
||||
}
|
||||
|
||||
if (Str::length($name) === 0) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'file.changeName.empty'
|
||||
]);
|
||||
}
|
||||
|
||||
$parent = $file->parent();
|
||||
$duplicate = $parent->files()->not($file)->findBy('filename', $name . '.' . $file->extension());
|
||||
|
||||
@@ -67,15 +73,22 @@ class FileRules
|
||||
* Validates if the file can be created
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param \Kirby\Image\Image $upload
|
||||
* @param \Kirby\Filesystem\File $upload
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\DuplicateException If a file with the same name exists
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to create the file
|
||||
*/
|
||||
public static function create(File $file, Image $upload): bool
|
||||
public static function create(File $file, BaseFile $upload): bool
|
||||
{
|
||||
if ($file->exists() === true) {
|
||||
throw new DuplicateException('The file exists and cannot be overwritten');
|
||||
if ($file->sha1() !== $upload->sha1()) {
|
||||
throw new DuplicateException([
|
||||
'key' => 'file.duplicate',
|
||||
'data' => [
|
||||
'filename' => $file->filename()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($file->permissions()->create() !== true) {
|
||||
@@ -110,12 +123,12 @@ class FileRules
|
||||
* Validates if the file can be replaced
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param \Kirby\Image\Image $upload
|
||||
* @param \Kirby\Filesystem\File $upload
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to replace the file
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the file type of the new file is different
|
||||
*/
|
||||
public static function replace(File $file, Image $upload): bool
|
||||
public static function replace(File $file, BaseFile $upload): bool
|
||||
{
|
||||
if ($file->permissions()->replace() !== true) {
|
||||
throw new PermissionException('The file cannot be replaced');
|
||||
@@ -169,34 +182,38 @@ class FileRules
|
||||
// make it easier to compare the extension
|
||||
$extension = strtolower($extension);
|
||||
|
||||
if (empty($extension)) {
|
||||
if (empty($extension) === true) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'file.extension.missing',
|
||||
'data' => ['filename' => $file->filename()]
|
||||
]);
|
||||
}
|
||||
|
||||
if (V::in($extension, ['php', 'phar', 'html', 'htm', 'exe', App::instance()->contentExtension()])) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'file.extension.forbidden',
|
||||
'data' => ['extension' => $extension]
|
||||
]);
|
||||
}
|
||||
|
||||
if (Str::contains($extension, 'php') || Str::contains($extension, 'phar')) {
|
||||
if (
|
||||
Str::contains($extension, 'php') !== false ||
|
||||
Str::contains($extension, 'phar') !== false ||
|
||||
Str::contains($extension, 'phtml') !== false
|
||||
) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'file.type.forbidden',
|
||||
'data' => ['type' => 'PHP']
|
||||
]);
|
||||
}
|
||||
|
||||
if (Str::contains($extension, 'htm')) {
|
||||
if (Str::contains($extension, 'htm') !== false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'file.type.forbidden',
|
||||
'data' => ['type' => 'HTML']
|
||||
]);
|
||||
}
|
||||
|
||||
if (V::in($extension, ['exe', App::instance()->contentExtension()]) !== false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'file.extension.forbidden',
|
||||
'data' => ['extension' => $extension]
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Filesystem\IsFile;
|
||||
|
||||
/**
|
||||
* FileVersion
|
||||
@@ -15,10 +15,7 @@ use Kirby\Toolkit\Properties;
|
||||
*/
|
||||
class FileVersion
|
||||
{
|
||||
use FileFoundation {
|
||||
toArray as parentToArray;
|
||||
}
|
||||
use Properties;
|
||||
use IsFile;
|
||||
|
||||
protected $modifications;
|
||||
protected $original;
|
||||
@@ -47,8 +44,8 @@ class FileVersion
|
||||
return $this->asset()->$method(...$arguments);
|
||||
}
|
||||
|
||||
// content fields
|
||||
if (is_a($this->original(), 'Kirby\Cms\File') === true) {
|
||||
// content fields
|
||||
return $this->original()->content()->get($method, $arguments);
|
||||
}
|
||||
}
|
||||
@@ -101,7 +98,11 @@ class FileVersion
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$this->kirby()->thumb($this->original()->root(), $this->root(), $this->modifications());
|
||||
$this->kirby()->thumb(
|
||||
$this->original()->root(),
|
||||
$this->root(),
|
||||
$this->modifications()
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -132,7 +133,7 @@ class FileVersion
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = array_merge($this->parentToArray(), [
|
||||
$array = array_merge($this->asset()->toArray(), [
|
||||
'modifications' => $this->modifications(),
|
||||
]);
|
||||
|
||||
|
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* The `$files` object extends the general
|
||||
* `Collection` class and refers to a
|
||||
@@ -30,12 +33,13 @@ class Files extends Collection
|
||||
* an entire second collection to the
|
||||
* current collection
|
||||
*
|
||||
* @param mixed $object
|
||||
* @param \Kirby\Cms\Files|\Kirby\Cms\File|string $object
|
||||
* @return $this
|
||||
* @throws \Kirby\Exception\InvalidArgumentException When no `File` or `Files` object or an ID of an existing file is passed
|
||||
*/
|
||||
public function add($object)
|
||||
{
|
||||
// add a page collection
|
||||
// add a files collection
|
||||
if (is_a($object, self::class) === true) {
|
||||
$this->data = array_merge($this->data, $object->data);
|
||||
|
||||
@@ -46,6 +50,11 @@ class Files extends Collection
|
||||
// add a file object
|
||||
} elseif (is_a($object, 'Kirby\Cms\File') === true) {
|
||||
$this->__set($object->id(), $object);
|
||||
|
||||
// give a useful error message on invalid input;
|
||||
// silently ignore "empty" values for compatibility with existing setups
|
||||
} elseif (in_array($object, [null, false, true], true) !== true) {
|
||||
throw new InvalidArgumentException('You must pass a Files or File object or an ID of an existing file to the Files collection');
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -120,6 +129,44 @@ class Files extends Collection
|
||||
return $this->findById($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file size for all
|
||||
* files in the collection in a
|
||||
* human-readable format
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function niceSize(): string
|
||||
{
|
||||
return F::niceSize($this->size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw size for all
|
||||
* files in the collection
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function size(): int
|
||||
{
|
||||
return F::size($this->values(function ($file) {
|
||||
return $file->root();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collection sorted by
|
||||
* the sort number and the filename
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function sorted()
|
||||
{
|
||||
return $this->sort('sort', 'asc', 'filename', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter all files by the given template
|
||||
*
|
||||
|
191
kirby/src/Cms/Find.php
Executable file
191
kirby/src/Cms/Find.php
Executable file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The Find class is used in the API and
|
||||
* the Panel to find models and parents
|
||||
* based on request paths
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Find
|
||||
{
|
||||
/**
|
||||
* Returns the file object for the given
|
||||
* parent path and filename
|
||||
*
|
||||
* @param string|null $path Path to file's parent model
|
||||
* @param string $filename Filename
|
||||
* @return \Kirby\Cms\File|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the file cannot be found
|
||||
*/
|
||||
public static function file(string $path = null, string $filename)
|
||||
{
|
||||
$filename = urldecode($filename);
|
||||
$file = static::parent($path)->file($filename);
|
||||
|
||||
if ($file && $file->isReadable() === true) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'file.notFound',
|
||||
'data' => [
|
||||
'filename' => $filename
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language object for the given code
|
||||
*
|
||||
* @param string $code Language code
|
||||
* @return \Kirby\Cms\Language|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the language cannot be found
|
||||
*/
|
||||
public static function language(string $code)
|
||||
{
|
||||
if ($language = App::instance()->language($code)) {
|
||||
return $language;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'language.notFound',
|
||||
'data' => [
|
||||
'code' => $code
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page object for the given id
|
||||
*
|
||||
* @param string $id Page's id
|
||||
* @return \Kirby\Cms\Page|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the page cannot be found
|
||||
*/
|
||||
public static function page(string $id)
|
||||
{
|
||||
$id = str_replace(['+', ' '], '/', $id);
|
||||
$page = App::instance()->page($id);
|
||||
|
||||
if ($page && $page->isReadable() === true) {
|
||||
return $page;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'page.notFound',
|
||||
'data' => [
|
||||
'slug' => $id
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model's object for the given path
|
||||
*
|
||||
* @param string $path Path to parent model
|
||||
* @return \Kirby\Cms\Model|null
|
||||
* @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid
|
||||
* @throws \Kirby\Exception\NotFoundException if the model cannot be found
|
||||
*/
|
||||
public static function parent(string $path)
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
$modelType = in_array($path, ['site', 'account']) ? $path : trim(dirname($path), '/');
|
||||
$modelTypes = [
|
||||
'site' => 'site',
|
||||
'users' => 'user',
|
||||
'pages' => 'page',
|
||||
'account' => 'account'
|
||||
];
|
||||
|
||||
$modelName = $modelTypes[$modelType] ?? null;
|
||||
|
||||
if (Str::endsWith($modelType, '/files') === true) {
|
||||
$modelName = 'file';
|
||||
}
|
||||
|
||||
$kirby = App::instance();
|
||||
|
||||
switch ($modelName) {
|
||||
case 'site':
|
||||
$model = $kirby->site();
|
||||
break;
|
||||
case 'account':
|
||||
$model = static::user();
|
||||
break;
|
||||
case 'page':
|
||||
$model = static::page(basename($path));
|
||||
break;
|
||||
case 'file':
|
||||
$model = static::file(...explode('/files/', $path));
|
||||
break;
|
||||
case 'user':
|
||||
$model = $kirby->user(basename($path));
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid model type: ' . $modelType);
|
||||
}
|
||||
|
||||
if ($model) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => $modelName . '.undefined'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user object for the given id or
|
||||
* returns the current authenticated user if no
|
||||
* id is passed
|
||||
*
|
||||
* @param string|null $id User's id
|
||||
* @return \Kirby\Cms\User|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the user for the given id cannot be found
|
||||
*/
|
||||
public static function user(string $id = null)
|
||||
{
|
||||
// account is a reserved word to find the current
|
||||
// user. It's used in various API and area routes.
|
||||
if ($id === 'account') {
|
||||
$id = null;
|
||||
}
|
||||
|
||||
$kirby = App::instance();
|
||||
|
||||
// get the authenticated user
|
||||
if ($id === null) {
|
||||
if ($user = $kirby->user(null, $kirby->option('api.allowImpersonation', false))) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'user.undefined'
|
||||
]);
|
||||
}
|
||||
|
||||
// get a specific user by id
|
||||
if ($user = $kirby->user($id)) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
throw new NotFoundException([
|
||||
'key' => 'user.notFound',
|
||||
'data' => [
|
||||
'name' => $id
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
@@ -1,130 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Form\Form as BaseForm;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* Extension of `Kirby\Form\Form` that introduces
|
||||
* a Form::for method that creates a proper form
|
||||
* definition for any Cms Model.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Form extends BaseForm
|
||||
{
|
||||
protected $errors;
|
||||
protected $fields;
|
||||
protected $values = [];
|
||||
|
||||
/**
|
||||
* Form constructor.
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$kirby = App::instance();
|
||||
|
||||
if ($kirby->multilang() === true) {
|
||||
$fields = $props['fields'] ?? [];
|
||||
$languageCode = $props['language'] ?? $kirby->language()->code();
|
||||
$isDefaultLanguage = $languageCode === $kirby->defaultLanguage()->code();
|
||||
|
||||
foreach ($fields as $fieldName => $fieldProps) {
|
||||
// switch untranslatable fields to readonly
|
||||
if (($fieldProps['translate'] ?? true) === false && $isDefaultLanguage === false) {
|
||||
$fields[$fieldName]['unset'] = true;
|
||||
$fields[$fieldName]['disabled'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$props['fields'] = $fields;
|
||||
}
|
||||
|
||||
parent::__construct($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field object by name
|
||||
* and handle nested fields correctly
|
||||
*
|
||||
* @param string $name
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @return \Kirby\Form\Field
|
||||
*/
|
||||
public function field(string $name)
|
||||
{
|
||||
$form = $this;
|
||||
$fieldNames = Str::split($name, '+');
|
||||
$index = 0;
|
||||
$count = count($fieldNames);
|
||||
$field = null;
|
||||
|
||||
foreach ($fieldNames as $fieldName) {
|
||||
$index++;
|
||||
|
||||
if ($field = $form->fields()->get($fieldName)) {
|
||||
if ($count !== $index) {
|
||||
$form = $field->form();
|
||||
}
|
||||
} else {
|
||||
throw new NotFoundException('The field "' . $fieldName . '" could not be found');
|
||||
}
|
||||
}
|
||||
|
||||
// it can get this error only if $name is an empty string as $name = ''
|
||||
if ($field === null) {
|
||||
throw new NotFoundException('No field could be loaded');
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\Model $model
|
||||
* @param array $props
|
||||
* @return static
|
||||
*/
|
||||
public static function for(Model $model, array $props = [])
|
||||
{
|
||||
// get the original model data
|
||||
$original = $model->content($props['language'] ?? null)->toArray();
|
||||
$values = $props['values'] ?? [];
|
||||
|
||||
// convert closures to values
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_a($value, 'Closure') === true) {
|
||||
$values[$key] = $value($original[$key] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
// set a few defaults
|
||||
$props['values'] = array_merge($original, $values);
|
||||
$props['fields'] = $props['fields'] ?? [];
|
||||
$props['model'] = $model;
|
||||
|
||||
// search for the blueprint
|
||||
if (method_exists($model, 'blueprint') === true && $blueprint = $model->blueprint()) {
|
||||
$props['fields'] = $blueprint->fields();
|
||||
}
|
||||
|
||||
$ignoreDisabled = $props['ignoreDisabled'] ?? false;
|
||||
|
||||
// REFACTOR: this could be more elegant
|
||||
if ($ignoreDisabled === true) {
|
||||
$props['fields'] = array_map(function ($field) {
|
||||
$field['disabled'] = false;
|
||||
return $field;
|
||||
}, $props['fields']);
|
||||
}
|
||||
|
||||
return new static($props);
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -16,21 +17,21 @@ use Kirby\Toolkit\Str;
|
||||
trait HasChildren
|
||||
{
|
||||
/**
|
||||
* The Pages collection
|
||||
* The list of available published children
|
||||
*
|
||||
* @var \Kirby\Cms\Pages
|
||||
*/
|
||||
public $children;
|
||||
|
||||
/**
|
||||
* The list of available drafts
|
||||
* The list of available draft children
|
||||
*
|
||||
* @var \Kirby\Cms\Pages
|
||||
*/
|
||||
public $drafts;
|
||||
|
||||
/**
|
||||
* Returns the Pages collection
|
||||
* Returns all published children
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
@@ -44,7 +45,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all children and drafts at the same time
|
||||
* Returns all published and draft children at the same time
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
@@ -54,8 +55,8 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of ids for the model's
|
||||
* toArray method
|
||||
* Returns a list of IDs for the model's
|
||||
* `toArray` method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -65,7 +66,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a child draft by id
|
||||
* Searches for a draft child by ID
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Kirby\Cms\Page|null
|
||||
@@ -99,7 +100,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all drafts of the model
|
||||
* Returns all draft children
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
@@ -123,7 +124,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds one or multiple children by id
|
||||
* Finds one or multiple published children by ID
|
||||
*
|
||||
* @param string ...$arguments
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Pages|null
|
||||
@@ -134,7 +135,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a single page or draft
|
||||
* Finds a single published or draft child
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Kirby\Cms\Page|null
|
||||
@@ -145,7 +146,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all children of children
|
||||
* Returns a collection of all published children of published children
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
@@ -155,7 +156,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the model has any children
|
||||
* Checks if the model has any published children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -165,7 +166,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the model has any drafts
|
||||
* Checks if the model has any draft children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -197,7 +198,7 @@ trait HasChildren
|
||||
/**
|
||||
* Creates a flat child index
|
||||
*
|
||||
* @param bool $drafts
|
||||
* @param bool $drafts If set to `true`, draft children are included
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function index(bool $drafts = false)
|
||||
@@ -210,7 +211,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Children collection
|
||||
* Sets the published children collection
|
||||
*
|
||||
* @param array|null $children
|
||||
* @return $this
|
||||
@@ -225,7 +226,7 @@ trait HasChildren
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Drafts collection
|
||||
* Sets the draft children collection
|
||||
*
|
||||
* @param array|null $drafts
|
||||
* @return $this
|
||||
|
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
/**
|
||||
* Extended KirbyTag class to provide
|
||||
* common helpers for tag objects
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class KirbyTag extends \Kirby\Text\KirbyTag
|
||||
{
|
||||
/**
|
||||
* Finds a file for the given path.
|
||||
* The method first searches the file
|
||||
* in the current parent, if it's a page.
|
||||
* Afterwards it uses Kirby's global file finder.
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Kirby\Cms\File|null
|
||||
*/
|
||||
public function file(string $path)
|
||||
{
|
||||
$parent = $this->parent();
|
||||
|
||||
if (
|
||||
is_object($parent) === true &&
|
||||
method_exists($parent, 'file') === true &&
|
||||
$file = $parent->file($path)
|
||||
) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
if (is_a($parent, 'Kirby\Cms\File') === true && $file = $parent->page()->file($path)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $this->kirby()->file($path, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Kirby instance
|
||||
*
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
public function kirby()
|
||||
{
|
||||
return $this->data['kirby'] ?? App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent model
|
||||
*
|
||||
* @return \Kirby\Cms\Model|null
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->data['parent'];
|
||||
}
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
/**
|
||||
* Extension of `Kirby\Text\KirbyTags` that introduces
|
||||
* `kirbytags:before` and `kirbytags:after` hooks
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class KirbyTags extends \Kirby\Text\KirbyTags
|
||||
{
|
||||
/**
|
||||
* The KirbyTag rendering class
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $tagClass = 'Kirby\Cms\KirbyTag';
|
||||
|
||||
/**
|
||||
* @param string|null $text
|
||||
* @param array $data
|
||||
* @param array $options
|
||||
* @param \Kirby\Cms\App|null $app
|
||||
* @return string
|
||||
*/
|
||||
public static function parse(string $text = null, array $data = [], array $options = [], ?App $app = null): string
|
||||
{
|
||||
if ($app !== null) {
|
||||
$text = $app->apply('kirbytags:before', compact('text', 'data', 'options'), 'text');
|
||||
}
|
||||
|
||||
$text = parent::parse($text, $data, $options);
|
||||
|
||||
if ($app !== null) {
|
||||
$text = $app->apply('kirbytags:after', compact('text', 'data', 'options'), 'text');
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ namespace Kirby\Cms;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Locale;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
@@ -221,6 +221,9 @@ class Language extends Model
|
||||
static::converter('', $language->code());
|
||||
}
|
||||
|
||||
// update the main languages collection in the app instance
|
||||
App::instance()->languages(false)->append($language->code(), $language);
|
||||
|
||||
return $language;
|
||||
}
|
||||
|
||||
@@ -234,23 +237,25 @@ class Language extends Model
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
if ($this->exists() === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$kirby = App::instance();
|
||||
$languages = $kirby->languages();
|
||||
$code = $this->code();
|
||||
$isLast = $languages->count() === 1;
|
||||
|
||||
if (F::remove($this->root()) !== true) {
|
||||
throw new Exception('The language could not be deleted');
|
||||
}
|
||||
|
||||
if ($languages->count() === 1) {
|
||||
return $this->converter($code, '');
|
||||
if ($isLast === true) {
|
||||
$this->converter($code, '');
|
||||
} else {
|
||||
return $this->deleteContentFiles($code);
|
||||
$this->deleteContentFiles($code);
|
||||
}
|
||||
|
||||
// get the original language collection and remove the current language
|
||||
$kirby->languages(false)->remove($code);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -679,6 +684,11 @@ class Language extends Model
|
||||
throw new PermissionException('Please select another language to be the primary language');
|
||||
}
|
||||
|
||||
return $updated->save();
|
||||
$language = $updated->save();
|
||||
|
||||
// make sure the language is also updated in the Kirby language collection
|
||||
App::instance()->languages(false)->set($language->code(), $language);
|
||||
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ class LanguageRouter
|
||||
return $page->uri($language) . '/' . $pattern;
|
||||
}, $patterns);
|
||||
|
||||
// reinject the pattern and the full page object
|
||||
// re-inject the pattern and the full page object
|
||||
$routes[$index]['pattern'] = $patterns;
|
||||
$routes[$index]['page'] = $page;
|
||||
} else {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
class LanguageRoutes
|
||||
{
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* A collection of all defined site languages
|
||||
|
@@ -17,6 +17,8 @@ class Layout extends Item
|
||||
{
|
||||
const ITEMS_CLASS = '\Kirby\Cms\Layouts';
|
||||
|
||||
use HasMethods;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Content
|
||||
*/
|
||||
@@ -36,6 +38,11 @@ class Layout extends Item
|
||||
*/
|
||||
public function __call(string $method, array $args = [])
|
||||
{
|
||||
// layout methods
|
||||
if ($this->hasMethod($method) === true) {
|
||||
return $this->callMethod($method, $args);
|
||||
}
|
||||
|
||||
return $this->attrs()->get($method);
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,8 @@ class LayoutColumn extends Item
|
||||
{
|
||||
const ITEMS_CLASS = '\Kirby\Cms\LayoutColumns';
|
||||
|
||||
use HasMethods;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Blocks
|
||||
*/
|
||||
@@ -45,13 +47,33 @@ class LayoutColumn extends Item
|
||||
$this->width = $params['width'] ?? '1/1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter function
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, $args)
|
||||
{
|
||||
// layout column methods
|
||||
if ($this->hasMethod($method) === true) {
|
||||
return $this->callMethod($method, $args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blocks collection
|
||||
*
|
||||
* @param bool $includeHidden Sets whether to include hidden blocks
|
||||
* @return \Kirby\Cms\Blocks
|
||||
*/
|
||||
public function blocks()
|
||||
public function blocks(bool $includeHidden = false)
|
||||
{
|
||||
if ($includeHidden === false) {
|
||||
return $this->blocks->filter('isHidden', false);
|
||||
}
|
||||
|
||||
return $this->blocks;
|
||||
}
|
||||
|
||||
@@ -104,7 +126,7 @@ class LayoutColumn extends Item
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'blocks' => $this->blocks()->toArray(),
|
||||
'blocks' => $this->blocks(true)->toArray(),
|
||||
'id' => $this->id(),
|
||||
'width' => $this->width(),
|
||||
];
|
||||
|
@@ -41,6 +41,18 @@ class Layouts extends Items
|
||||
return parent::factory($items, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given block type exists in the layouts collection
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public function hasBlockType(string $type): bool
|
||||
{
|
||||
return $this->toBlocks()->hasType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse layouts data
|
||||
*
|
||||
@@ -63,4 +75,28 @@ class Layouts extends Items
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts layouts to blocks
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param bool $includeHidden Sets whether to include hidden blocks
|
||||
* @return \Kirby\Cms\Blocks
|
||||
*/
|
||||
public function toBlocks(bool $includeHidden = false)
|
||||
{
|
||||
$blocks = [];
|
||||
|
||||
if ($this->isNotEmpty() === true) {
|
||||
foreach ($this->data() as $layout) {
|
||||
foreach ($layout->columns() as $column) {
|
||||
foreach ($column->blocks($includeHidden) as $block) {
|
||||
$blocks[] = $block->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Blocks::factory($blocks);
|
||||
}
|
||||
}
|
||||
|
250
kirby/src/Cms/Loader.php
Executable file
250
kirby/src/Cms/Loader.php
Executable file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* The Loader class is an internal loader for
|
||||
* core parts, like areas, components, sections, etc.
|
||||
*
|
||||
* It's exposed in the `$kirby->load()` and the
|
||||
* `$kirby->core()->load()` methods.
|
||||
*
|
||||
* With `$kirby->load()` you get access to core parts
|
||||
* that might be overwritten by plugins.
|
||||
*
|
||||
* With `$kirby->core()->load()` you get access to
|
||||
* untouched core parts. This is useful if you want to
|
||||
* reuse or fall back to core features in your plugins.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Loader
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\App
|
||||
*/
|
||||
protected $kirby;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $withPlugins;
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @param bool $withPlugins
|
||||
*/
|
||||
public function __construct(App $kirby, bool $withPlugins = true)
|
||||
{
|
||||
$this->kirby = $kirby;
|
||||
$this->withPlugins = $withPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the area definition
|
||||
*
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function area(string $name): ?array
|
||||
{
|
||||
return $this->areas()[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all areas and makes sure that plugins
|
||||
* are injected properly
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function areas(): array
|
||||
{
|
||||
$areas = [];
|
||||
$extensions = $this->withPlugins === true ? $this->kirby->extensions('areas') : [];
|
||||
|
||||
// load core areas and extend them with elements from plugins if they exist
|
||||
foreach ($this->kirby->core()->areas() as $id => $area) {
|
||||
$area = $this->resolveArea($area);
|
||||
|
||||
if (isset($extensions[$id]) === true) {
|
||||
foreach ($extensions[$id] as $areaExtension) {
|
||||
$extension = $this->resolveArea($areaExtension);
|
||||
$area = array_replace_recursive($area, $extension);
|
||||
}
|
||||
|
||||
unset($extensions[$id]);
|
||||
}
|
||||
|
||||
$areas[$id] = $area;
|
||||
}
|
||||
|
||||
// add additional areas from plugins
|
||||
foreach ($extensions as $id => $areaExtensions) {
|
||||
foreach ($areaExtensions as $areaExtension) {
|
||||
$areas[$id] = $this->resolve($areaExtension);
|
||||
}
|
||||
}
|
||||
|
||||
return $areas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a core component closure
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Closure|null
|
||||
*/
|
||||
public function component(string $name): ?Closure
|
||||
{
|
||||
return $this->extension('components', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all core component closures
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function components(): array
|
||||
{
|
||||
return $this->extensions('components');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a particular extension
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function extension(string $type, string $name)
|
||||
{
|
||||
return $this->extensions($type)[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all defined extensions
|
||||
*
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function extensions(string $type): array
|
||||
{
|
||||
return $this->withPlugins === false ? $this->kirby->core()->$type() : $this->kirby->extensions($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* The resolver takes a string, array or closure.
|
||||
*
|
||||
* 1.) a string is supposed to be a path to an existing file.
|
||||
* The file will either be included when it's a PHP file and
|
||||
* the array contents will be read. Or it will be parsed with
|
||||
* the Data class to read yml or json data into an array
|
||||
*
|
||||
* 2.) arrays are untouched and returned
|
||||
*
|
||||
* 3.) closures will be called and the Kirby instance will be
|
||||
* passed as first argument
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return mixed
|
||||
*/
|
||||
public function resolve($item)
|
||||
{
|
||||
if (is_string($item) === true) {
|
||||
if (F::extension($item) !== 'php') {
|
||||
$item = Data::read($item);
|
||||
} else {
|
||||
$item = require $item;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_callable($item)) {
|
||||
$item = $item($this->kirby);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `static::resolve()` on all items
|
||||
* in the given array
|
||||
*
|
||||
* @param array $items
|
||||
* @return array
|
||||
*/
|
||||
public function resolveAll(array $items): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($items as $key => $value) {
|
||||
$result[$key] = $this->resolve($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Areas need a bit of special treatment
|
||||
* when they are being loaded
|
||||
*
|
||||
* @param string|array|Closure $area
|
||||
* @return array
|
||||
*/
|
||||
public function resolveArea($area): array
|
||||
{
|
||||
$area = $this->resolve($area);
|
||||
$dropdowns = $area['dropdowns'] ?? [];
|
||||
|
||||
// convert closure dropdowns to an array definition
|
||||
// otherwise they cannot be merged properly later
|
||||
foreach ($dropdowns as $key => $dropdown) {
|
||||
if (is_a($dropdown, 'Closure') === true) {
|
||||
$area['dropdowns'][$key] = [
|
||||
'options' => $dropdown
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a particular section definition
|
||||
*
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function section(string $name): ?array
|
||||
{
|
||||
return $this->resolve($this->extension('sections', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all section defintions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sections(): array
|
||||
{
|
||||
return $this->resolveAll($this->extensions('sections'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status flag, which shows
|
||||
* if plugins are loaded as well.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function withPlugins(): bool
|
||||
{
|
||||
return $this->withPlugins;
|
||||
}
|
||||
}
|
@@ -3,8 +3,8 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
@@ -48,8 +48,8 @@ class Media
|
||||
if (Str::startsWith($hash, $file->mediaToken() . '-') === true) {
|
||||
return Response::redirect($file->mediaUrl(), 307);
|
||||
} else {
|
||||
// don't leak the correct token
|
||||
return new Response('Not Found', 'text/plain', 404);
|
||||
// don't leak the correct token, render the error page
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,14 @@ abstract class Model
|
||||
{
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* Each model must define a CLASS_ALIAS
|
||||
* which will be used in template queries.
|
||||
* The CLASS_ALIAS is a short human-readable
|
||||
* version of the class name. I.e. page.
|
||||
*/
|
||||
const CLASS_ALIAS = null;
|
||||
|
||||
/**
|
||||
* The parent Kirby instance
|
||||
*
|
||||
|
@@ -5,6 +5,7 @@ namespace Kirby\Cms;
|
||||
use Closure;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
@@ -19,14 +20,6 @@ use Throwable;
|
||||
*/
|
||||
abstract class ModelWithContent extends Model
|
||||
{
|
||||
/**
|
||||
* Each model must define a CLASS_ALIAS
|
||||
* which will be used in template queries.
|
||||
* The CLASS_ALIAS is a short human-readable
|
||||
* version of the class name. I.e. page.
|
||||
*/
|
||||
const CLASS_ALIAS = null;
|
||||
|
||||
/**
|
||||
* The content
|
||||
*
|
||||
@@ -244,46 +237,6 @@ abstract class ModelWithContent extends Model
|
||||
return $this->update([$field => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drag text from a custom callback
|
||||
* if the callback is defined in the config
|
||||
*
|
||||
* @internal
|
||||
* @param string $type markdown or kirbytext
|
||||
* @param mixed ...$args
|
||||
* @return string|null
|
||||
*/
|
||||
public function dragTextFromCallback(string $type, ...$args): ?string
|
||||
{
|
||||
$dragTextCallback = option('panel.' . $type . '.' . static::CLASS_ALIAS . 'DragText');
|
||||
|
||||
if (empty($dragTextCallback) === false && is_a($dragTextCallback, 'Closure') === true && ($dragText = $dragTextCallback($this, ...$args)) !== null) {
|
||||
return $dragText;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct drag text type
|
||||
* depending on the given type or the
|
||||
* configuration
|
||||
*
|
||||
* @internal
|
||||
* @param string $type (null|auto|kirbytext|markdown)
|
||||
* @return string
|
||||
*/
|
||||
public function dragTextType(string $type = null): string
|
||||
{
|
||||
$type = $type ?? 'auto';
|
||||
|
||||
if ($type === 'auto') {
|
||||
$type = option('panel.kirbytext', true) ? 'kirbytext' : 'markdown';
|
||||
}
|
||||
|
||||
return $type === 'markdown' ? 'markdown' : 'kirbytext';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all content validation errors
|
||||
*
|
||||
@@ -362,144 +315,12 @@ abstract class ModelWithContent extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the panel icon definition
|
||||
* Returns the panel info of the model
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
* @return \Kirby\Panel\Model
|
||||
*/
|
||||
public function panelIcon(array $params = null): array
|
||||
{
|
||||
$defaults = [
|
||||
'type' => 'page',
|
||||
'ratio' => null,
|
||||
'back' => 'pattern',
|
||||
'color' => '#c5c9c6',
|
||||
];
|
||||
|
||||
return array_merge($defaults, $params ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param string|array|false|null $settings
|
||||
* @return array|null
|
||||
*/
|
||||
public function panelImage($settings = null): ?array
|
||||
{
|
||||
$defaults = [
|
||||
'ratio' => '3/2',
|
||||
'back' => 'pattern',
|
||||
'cover' => false
|
||||
];
|
||||
|
||||
// switch the image off
|
||||
if ($settings === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_string($settings) === true) {
|
||||
// use defined icon in blueprint
|
||||
if ($settings === 'icon') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$settings = [
|
||||
'query' => $settings
|
||||
];
|
||||
}
|
||||
|
||||
if ($image = $this->panelImageSource($settings['query'] ?? null)) {
|
||||
|
||||
// main url
|
||||
$settings['url'] = $image->url();
|
||||
|
||||
// only create srcsets for actual File objects
|
||||
if (is_a($image, 'Kirby\Cms\File') === true) {
|
||||
|
||||
// for cards
|
||||
$settings['cards'] = [
|
||||
'url' => 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw',
|
||||
'srcset' => $image->srcset([
|
||||
352,
|
||||
864,
|
||||
1408,
|
||||
])
|
||||
];
|
||||
|
||||
// for lists
|
||||
$settings['list'] = [
|
||||
'url' => 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw',
|
||||
'srcset' => $image->srcset([
|
||||
'1x' => [
|
||||
'width' => 38,
|
||||
'height' => 38,
|
||||
'crop' => 'center'
|
||||
],
|
||||
'2x' => [
|
||||
'width' => 76,
|
||||
'height' => 76,
|
||||
'crop' => 'center'
|
||||
],
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
unset($settings['query']);
|
||||
}
|
||||
|
||||
return array_merge($defaults, (array)$settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Cms\Asset|null
|
||||
*/
|
||||
protected function panelImageSource(string $query = null)
|
||||
{
|
||||
$image = $this->query($query ?? null);
|
||||
|
||||
// validate the query result
|
||||
if (is_a($image, 'Kirby\Cms\File') === false && is_a($image, 'Kirby\Cms\Asset') === false) {
|
||||
$image = null;
|
||||
}
|
||||
|
||||
// fallback for files
|
||||
if ($image === null && is_a($this, 'Kirby\Cms\File') === true && $this->isViewable() === true) {
|
||||
$image = $this;
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
* This also checks for the lock status
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
*/
|
||||
public function panelOptions(array $unlock = []): array
|
||||
{
|
||||
$options = $this->permissions()->toArray();
|
||||
|
||||
if ($this->isLocked()) {
|
||||
foreach ($options as $key => $value) {
|
||||
if (in_array($key, $unlock)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[$key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
abstract public function panel();
|
||||
|
||||
/**
|
||||
* Must return the permissions object for the model
|
||||
@@ -687,24 +508,43 @@ abstract class ModelWithContent extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* String template builder
|
||||
* String template builder with automatic HTML escaping
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string|null $template
|
||||
* @param string|null $template Template string or `null` to use the model ID
|
||||
* @param array $data
|
||||
* @param string $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* @return string
|
||||
*/
|
||||
public function toString(string $template = null, array $data = [], string $fallback = ''): string
|
||||
public function toSafeString(string $template = null, array $data = [], string $fallback = ''): string
|
||||
{
|
||||
return $this->toString($template, $data, $fallback, 'safeTemplate');
|
||||
}
|
||||
|
||||
/**
|
||||
* String template builder
|
||||
*
|
||||
* @param string|null $template Template string or `null` to use the model ID
|
||||
* @param array $data
|
||||
* @param string $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* @param string $handler For internal use
|
||||
* @return string
|
||||
*/
|
||||
public function toString(string $template = null, array $data = [], string $fallback = '', string $handler = 'template'): string
|
||||
{
|
||||
if ($template === null) {
|
||||
return $this->id();
|
||||
return $this->id() ?? '';
|
||||
}
|
||||
|
||||
$result = Str::template($template, array_replace([
|
||||
if ($handler !== 'template' && $handler !== 'safeTemplate') {
|
||||
throw new InvalidArgumentException('Invalid toString handler'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$result = Str::$handler($template, array_replace([
|
||||
'kirby' => $this->kirby(),
|
||||
'site' => is_a($this, 'Kirby\Cms\Site') ? $this : $this->site(),
|
||||
static::CLASS_ALIAS => $this
|
||||
], $data), $fallback);
|
||||
], $data), ['fallback' => $fallback]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
@@ -801,4 +641,59 @@ abstract class ModelWithContent extends Model
|
||||
$this->contentFileData($data, $languageCode)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deprecated!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the panel icon definition
|
||||
*
|
||||
* @deprecated 3.6.0 Use `->panel()->image()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $params
|
||||
* @return array|null
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelIcon(array $params = null): ?array
|
||||
{
|
||||
return $this->panel()->image($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.6.0 Use `->panel()->image()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param string|array|false|null $settings
|
||||
* @return array|null
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelImage($settings = null): ?array
|
||||
{
|
||||
return $this->panel()->image($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
* This also checks for the lock status
|
||||
*
|
||||
* @deprecated 3.6.0 Use `->panel()->options()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelOptions(array $unlock = []): array
|
||||
{
|
||||
return $this->panel()->options($unlock);
|
||||
}
|
||||
}
|
||||
|
@@ -5,10 +5,11 @@ namespace Kirby\Cms;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Panel\Page as Panel;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
/**
|
||||
* The `$page` object is the heart and
|
||||
@@ -171,7 +172,7 @@ class Page extends ModelWithContent
|
||||
}
|
||||
|
||||
// return page content otherwise
|
||||
return $this->content()->get($method, $arguments);
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,9 +215,9 @@ class Page extends ModelWithContent
|
||||
public function apiUrl(bool $relative = false): string
|
||||
{
|
||||
if ($relative === true) {
|
||||
return 'pages/' . $this->panelId();
|
||||
return 'pages/' . $this->panel()->id();
|
||||
} else {
|
||||
return $this->kirby()->url('api') . '/pages/' . $this->panelId();
|
||||
return $this->kirby()->url('api') . '/pages/' . $this->panel()->id();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +336,7 @@ class Page extends ModelWithContent
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the controller returns invalid objects for `kirby`, `site`, `pages` or `page`
|
||||
*/
|
||||
public function controller($data = [], $contentType = 'html'): array
|
||||
public function controller(array $data = [], string $contentType = 'html'): array
|
||||
{
|
||||
// create the template data
|
||||
$data = array_merge($data, [
|
||||
@@ -426,31 +427,6 @@ class Page extends ModelWithContent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the page, which will be
|
||||
* used in the panel, when the page
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (null|auto|kirbytext|markdown)
|
||||
* @return string
|
||||
*/
|
||||
public function dragText(string $type = null): string
|
||||
{
|
||||
$type = $this->dragTextType($type);
|
||||
|
||||
if ($dragTextFromCallback = $this->dragTextFromCallback($type)) {
|
||||
return $dragTextFromCallback;
|
||||
}
|
||||
|
||||
if ($type === 'markdown') {
|
||||
return '[' . $this->title() . '](' . $this->url() . ')';
|
||||
} else {
|
||||
return '(link: ' . $this->id() . ' text: ' . $this->title() . ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the page exists on disk
|
||||
*
|
||||
@@ -929,109 +905,13 @@ class Page extends ModelWithContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the panel icon definition
|
||||
* according to the blueprint settings
|
||||
* Returns the panel info object
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
* @return \Kirby\Panel\Page
|
||||
*/
|
||||
public function panelIcon(array $params = null): array
|
||||
public function panel()
|
||||
{
|
||||
if ($icon = $this->blueprint()->icon()) {
|
||||
$params['type'] = $icon;
|
||||
}
|
||||
|
||||
return parent::panelIcon($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the escaped Id, which is
|
||||
* used in the panel to make routing work properly
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function panelId(): string
|
||||
{
|
||||
return str_replace('/', '+', $this->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Cms\Asset|null
|
||||
*/
|
||||
protected function panelImageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
$query = 'page.image';
|
||||
}
|
||||
|
||||
return parent::panelImageSource($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function panelPath(): string
|
||||
{
|
||||
return 'pages/' . $this->panelId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the response data for page pickers
|
||||
* and page fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function panelPickerData(array $params = []): array
|
||||
{
|
||||
$image = $this->panelImage($params['image'] ?? []);
|
||||
$icon = $this->panelIcon($image);
|
||||
|
||||
// escape the default text
|
||||
// TODO: no longer needed in 3.6
|
||||
$textQuery = $params['text'] ?? '{{ page.title }}';
|
||||
$text = $this->toString($textQuery);
|
||||
if ($textQuery === '{{ page.title }}') {
|
||||
$text = Escape::html($text);
|
||||
}
|
||||
|
||||
return [
|
||||
'dragText' => $this->dragText(),
|
||||
'hasChildren' => $this->hasChildren(),
|
||||
'icon' => $icon,
|
||||
'id' => $this->id(),
|
||||
'image' => $image,
|
||||
'info' => $this->toString($params['info'] ?? false),
|
||||
'link' => $this->panelUrl(true),
|
||||
'text' => $text,
|
||||
'url' => $this->url(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
if ($relative === true) {
|
||||
return '/' . $this->panelPath();
|
||||
} else {
|
||||
return $this->kirby()->url('panel') . '/' . $this->panelPath();
|
||||
}
|
||||
return new Panel($this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1383,7 +1263,9 @@ class Page extends ModelWithContent
|
||||
$languageCode = $this->kirby()->languageCode();
|
||||
}
|
||||
|
||||
if ($translation = $this->translations()->find($languageCode)) {
|
||||
$defaultLanguageCode = $this->kirby()->defaultLanguage()->code();
|
||||
|
||||
if ($languageCode !== $defaultLanguageCode && $translation = $this->translations()->find($languageCode)) {
|
||||
return $translation->slug() ?? $this->slug;
|
||||
}
|
||||
}
|
||||
@@ -1576,4 +1458,97 @@ class Page extends ModelWithContent
|
||||
|
||||
return $this->url = $this->site()->urlForLanguage($language) . '/' . $this->slug($language);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deprecated!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the page, which will be
|
||||
* used in the panel, when the page
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @deprecated 3.6.0 Use `->panel()->dragText()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (null|auto|kirbytext|markdown)
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function dragText(string $type = null): string
|
||||
{
|
||||
return $this->panel()->dragText($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the escaped Id, which is
|
||||
* used in the panel to make routing work properly
|
||||
*
|
||||
* @deprecated 3.6.0 Use `->panel()->id()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelId(): string
|
||||
{
|
||||
return $this->panel()->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @deprecated 3.6.0 Use `->panel()->path()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelPath(): string
|
||||
{
|
||||
return $this->panel()->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the response data for page pickers
|
||||
* and page fields
|
||||
*
|
||||
* @deprecated 3.6.0 Use `->panel()->pickerData()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelPickerData(array $params = []): array
|
||||
{
|
||||
return $this->panel()->pickerData($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @deprecated 3.6.0 Use `->panel()->url()` instead
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
return $this->panel()->url($relative);
|
||||
}
|
||||
}
|
||||
|
@@ -7,8 +7,10 @@ use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -284,6 +286,10 @@ trait PageActions
|
||||
]);
|
||||
|
||||
foreach ($this->kirby()->languages()->codes() as $code) {
|
||||
if ($oldPage->translation($code)->exists() !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $oldPage->content($code)->convertTo($template);
|
||||
|
||||
if (F::remove($oldPage->contentFile($code)) !== true) {
|
||||
@@ -295,7 +301,7 @@ trait PageActions
|
||||
}
|
||||
|
||||
// return a fresh copy of the object
|
||||
return $newPage->clone();
|
||||
$page = $newPage->clone();
|
||||
} else {
|
||||
$newPage = $this->clone([
|
||||
'content' => $this->content()->convertTo($template),
|
||||
@@ -306,8 +312,17 @@ trait PageActions
|
||||
throw new LogicException('The old text file could not be removed');
|
||||
}
|
||||
|
||||
return $newPage->save();
|
||||
$page = $newPage->save();
|
||||
}
|
||||
|
||||
// update the parent collection
|
||||
if ($page->isDraft() === true) {
|
||||
$page->parentModel()->drafts()->set($page->id(), $page);
|
||||
} else {
|
||||
$page->parentModel()->children()->set($page->id(), $page);
|
||||
}
|
||||
|
||||
return $page;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -322,7 +337,16 @@ trait PageActions
|
||||
{
|
||||
$arguments = ['page' => $this, 'title' => $title, 'languageCode' => $languageCode];
|
||||
return $this->commit('changeTitle', $arguments, function ($page, $title, $languageCode) {
|
||||
return $page->save(['title' => $title], $languageCode);
|
||||
$page = $page->save(['title' => $title], $languageCode);
|
||||
|
||||
// flush the parent cache to get children and drafts right
|
||||
if ($page->isDraft() === true) {
|
||||
$page->parentModel()->drafts()->set($page->id(), $page);
|
||||
} else {
|
||||
$page->parentModel()->children()->set($page->id(), $page);
|
||||
}
|
||||
|
||||
return $page;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -529,14 +553,13 @@ trait PageActions
|
||||
return 0;
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
// the $format needs to produce only digits,
|
||||
// so it can be converted to integer below
|
||||
$format = $mode === 'date' ? 'Ymd' : 'YmdHi';
|
||||
$lang = $this->kirby()->defaultLanguage() ?? null;
|
||||
$field = $this->content($lang)->get('date');
|
||||
$date = $field->isEmpty() ? 'now' : $field;
|
||||
// TODO: in 3.6.0 throw an error if date() doesn't
|
||||
// return a number, see https://github.com/getkirby/kirby/pull/3061#discussion_r552783943
|
||||
return (int)date($format, strtotime($date));
|
||||
break;
|
||||
case 'default':
|
||||
|
||||
$max = $this
|
||||
@@ -571,7 +594,7 @@ trait PageActions
|
||||
'kirby' => $app,
|
||||
'page' => $app->page($this->id()),
|
||||
'site' => $app->site(),
|
||||
], '');
|
||||
], ['fallback' => '']);
|
||||
|
||||
return (int)$template;
|
||||
}
|
||||
@@ -640,17 +663,28 @@ trait PageActions
|
||||
{
|
||||
|
||||
// create the slug for the duplicate
|
||||
$slug = Str::slug($slug ?? $this->slug() . '-copy');
|
||||
$slug = Str::slug($slug ?? $this->slug() . '-' . Str::slug(t('page.duplicate.appendix')));
|
||||
|
||||
$arguments = [
|
||||
'originalPage' => $this,
|
||||
'input' => $slug,
|
||||
'options' => $options
|
||||
];
|
||||
|
||||
$arguments = ['originalPage' => $this, 'input' => $slug, 'options' => $options];
|
||||
return $this->commit('duplicate', $arguments, function ($page, $slug, $options) {
|
||||
return $this->copy([
|
||||
$page = $this->copy([
|
||||
'parent' => $this->parent(),
|
||||
'slug' => $slug,
|
||||
'isDraft' => true,
|
||||
'files' => $options['files'] ?? false,
|
||||
'children' => $options['children'] ?? false,
|
||||
]);
|
||||
|
||||
if (isset($options['title']) === true) {
|
||||
$page = $page->changeTitle($options['title']);
|
||||
}
|
||||
|
||||
return $page;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -786,20 +820,6 @@ trait PageActions
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.5.0 Use `Page::changeSort()` instead
|
||||
* @todo Remove in 3.6.0
|
||||
*
|
||||
* @param null $position
|
||||
* @return $this|static
|
||||
*/
|
||||
public function sort($position = null)
|
||||
{
|
||||
deprecated('$page->sort() is deprecated, use $page->changeSort() instead. $page->sort() will be removed in Kirby 3.6.0.');
|
||||
|
||||
return $this->changeStatus('listed', $position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a page from listed or
|
||||
* unlisted to draft.
|
||||
|
@@ -169,26 +169,12 @@ class PageRules
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($page->permissions()->changeStatus() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.changeStatus.permission',
|
||||
'data' => [
|
||||
'slug' => $page->slug()
|
||||
]
|
||||
]);
|
||||
}
|
||||
static::publish($page);
|
||||
|
||||
if ($position !== null && $position < 0) {
|
||||
throw new InvalidArgumentException(['key' => 'page.num.invalid']);
|
||||
}
|
||||
|
||||
if ($page->isDraft() === true && empty($page->errors()) === false) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.changeStatus.incomplete',
|
||||
'details' => $page->errors()
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -201,14 +187,7 @@ class PageRules
|
||||
*/
|
||||
public static function changeStatusToUnlisted(Page $page)
|
||||
{
|
||||
if ($page->permissions()->changeStatus() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.changeStatus.permission',
|
||||
'data' => [
|
||||
'slug' => $page->slug()
|
||||
]
|
||||
]);
|
||||
}
|
||||
static::publish($page);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -376,6 +355,34 @@ class PageRules
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page can be published
|
||||
* (status change from draft to listed or unlisted)
|
||||
*
|
||||
* @param Page $page
|
||||
* @return bool
|
||||
*/
|
||||
public static function publish(Page $page): bool
|
||||
{
|
||||
if ($page->permissions()->changeStatus() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.changeStatus.permission',
|
||||
'data' => [
|
||||
'slug' => $page->slug()
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
if ($page->isDraft() === true && empty($page->errors()) === false) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.changeStatus.incomplete',
|
||||
'details' => $page->errors()
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the page can be updated
|
||||
*
|
||||
|
@@ -48,13 +48,13 @@ class Pages extends Collection
|
||||
* an entire second collection to the
|
||||
* current collection
|
||||
*
|
||||
* @param mixed $object
|
||||
* @param \Kirby\Cms\Pages|\Kirby\Cms\Page|string $object
|
||||
* @return $this
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
* @throws \Kirby\Exception\InvalidArgumentException When no `Page` or `Pages` object or an ID of an existing page is passed
|
||||
*/
|
||||
public function add($object)
|
||||
{
|
||||
// add a page collection
|
||||
// add a pages collection
|
||||
if (is_a($object, self::class) === true) {
|
||||
$this->data = array_merge($this->data, $object->data);
|
||||
|
||||
@@ -66,9 +66,10 @@ class Pages extends Collection
|
||||
} elseif (is_a($object, 'Kirby\Cms\Page') === true) {
|
||||
$this->__set($object->id(), $object);
|
||||
|
||||
// give a useful error message on invalid input
|
||||
// give a useful error message on invalid input;
|
||||
// silently ignore "empty" values for compatibility with existing setups
|
||||
} elseif (in_array($object, [null, false, true], true) !== true) {
|
||||
throw new InvalidArgumentException('You must pass a Page object to the Pages collection');
|
||||
throw new InvalidArgumentException('You must pass a Pages or Page object or an ID of an existing page to the Pages collection');
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -223,14 +224,8 @@ class Pages extends Collection
|
||||
return $page;
|
||||
}
|
||||
|
||||
$multiLang = App::instance()->multilang();
|
||||
|
||||
if ($multiLang === true && $page = $this->findBy('slug', $id)) {
|
||||
return $page;
|
||||
}
|
||||
|
||||
$start = is_a($this->parent, 'Kirby\Cms\Page') === true ? $this->parent->id() : '';
|
||||
$page = $this->findByIdRecursive($id, $start, $multiLang);
|
||||
$page = $this->findByIdRecursive($id, $start, App::instance()->multilang());
|
||||
|
||||
return $page;
|
||||
}
|
||||
@@ -254,8 +249,14 @@ class Pages extends Collection
|
||||
$query = ltrim($query . '/' . $key, '/');
|
||||
$item = $collection->get($query) ?? null;
|
||||
|
||||
if ($item === null && $multiLang === true) {
|
||||
$item = $collection->findBy('slug', $key);
|
||||
if ($item === null && $multiLang === true && !App::instance()->language()->isDefault()) {
|
||||
if (count($path) > 1 || $collection->parent()) {
|
||||
// either the desired path is definitely not a slug, or collection is the children of another collection
|
||||
$item = $collection->findBy('slug', $key);
|
||||
} else {
|
||||
// desired path _could_ be a slug or a "top level" uri
|
||||
$item = $collection->findBy('uri', $key);
|
||||
}
|
||||
}
|
||||
|
||||
if ($item === null) {
|
||||
|
@@ -1,139 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\View;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* The Panel class is only responsible to create
|
||||
* a working panel view with all the right URLs
|
||||
* and other panel options. The view template is
|
||||
* located in `kirby/views/panel.php`
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Panel
|
||||
{
|
||||
/**
|
||||
* Returns custom css path for panel ui
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return string|false
|
||||
*/
|
||||
public static function customCss(App $kirby)
|
||||
{
|
||||
if ($css = $kirby->option('panel.css')) {
|
||||
$asset = asset($css);
|
||||
|
||||
if ($asset->exists() === true) {
|
||||
return $asset->url() . '?' . $asset->modified();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns predefined icons path as sprite svg file
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return string
|
||||
*/
|
||||
public static function icons(App $kirby): string
|
||||
{
|
||||
return F::read($kirby->root('kirby') . '/panel/dist/img/icons.svg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Links all dist files in the media folder
|
||||
* and returns the link to the requested asset
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return bool
|
||||
* @throws \Exception If Panel assets could not be moved to the public directory
|
||||
*/
|
||||
public static function link(App $kirby): bool
|
||||
{
|
||||
$mediaRoot = $kirby->root('media') . '/panel';
|
||||
$panelRoot = $kirby->root('panel') . '/dist';
|
||||
$versionHash = $kirby->versionHash();
|
||||
$versionRoot = $mediaRoot . '/' . $versionHash;
|
||||
|
||||
// check if the version already exists
|
||||
if (is_dir($versionRoot) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// delete the panel folder and all previous versions
|
||||
Dir::remove($mediaRoot);
|
||||
|
||||
// recreate the panel folder
|
||||
Dir::make($mediaRoot, true);
|
||||
|
||||
// create a symlink to the dist folder
|
||||
if (Dir::copy($panelRoot, $versionRoot) !== true) {
|
||||
throw new Exception('Panel assets could not be linked');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the main panel view
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function render(App $kirby)
|
||||
{
|
||||
try {
|
||||
if (static::link($kirby) === true) {
|
||||
usleep(1);
|
||||
go($kirby->url('index') . '/' . $kirby->path());
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
die('The Panel assets cannot be installed properly. ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// get the uri object for the panel url
|
||||
$uri = new Uri($url = $kirby->url('panel'));
|
||||
|
||||
// fetch all plugins
|
||||
$plugins = new PanelPlugins();
|
||||
|
||||
$view = new View($kirby->root('kirby') . '/views/panel.php', [
|
||||
'kirby' => $kirby,
|
||||
'config' => $kirby->option('panel'),
|
||||
'assetUrl' => $kirby->url('media') . '/panel/' . $kirby->versionHash(),
|
||||
'customCss' => static::customCss($kirby),
|
||||
'icons' => static::icons($kirby),
|
||||
'pluginCss' => $plugins->url('css'),
|
||||
'pluginJs' => $plugins->url('js'),
|
||||
'panelUrl' => $uri->path()->toString(true) . '/',
|
||||
'nonce' => $kirby->nonce(),
|
||||
'options' => [
|
||||
'url' => $url,
|
||||
'site' => $kirby->url('index'),
|
||||
'api' => $kirby->url('api'),
|
||||
'csrf' => $kirby->option('api.csrf') ?? csrf(),
|
||||
'translation' => 'en',
|
||||
'debug' => $kirby->option('debug', false),
|
||||
'search' => [
|
||||
'limit' => $kirby->option('panel.search.limit') ?? 10
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
return new Response($view->render());
|
||||
}
|
||||
}
|
@@ -27,10 +27,12 @@ class Permissions
|
||||
*/
|
||||
protected $actions = [
|
||||
'access' => [
|
||||
'panel' => true,
|
||||
'settings' => true,
|
||||
'site' => true,
|
||||
'users' => true,
|
||||
'account' => true,
|
||||
'languages' => true,
|
||||
'panel' => true,
|
||||
'site' => true,
|
||||
'system' => true,
|
||||
'users' => true,
|
||||
],
|
||||
'files' => [
|
||||
'changeName' => true,
|
||||
@@ -157,6 +159,12 @@ class Permissions
|
||||
*/
|
||||
protected function setAction(string $category, string $action, $setting)
|
||||
{
|
||||
// deprecated fallback for the settings/system view
|
||||
// TODO: remove in 3.7
|
||||
if ($category === 'access' && $action === 'settings') {
|
||||
$action = 'system';
|
||||
}
|
||||
|
||||
// wildcard to overwrite the entire category
|
||||
if ($action === '*') {
|
||||
return $this->setCategory($category, $setting);
|
||||
|
@@ -54,6 +54,8 @@ abstract class Picker
|
||||
'image' => [],
|
||||
// query template for the info field
|
||||
'info' => false,
|
||||
// listing style: list, cards, cardlets
|
||||
'layout' =>'list',
|
||||
// number of users displayed per pagination page
|
||||
'limit' => 20,
|
||||
// optional mapping function for the result array
|
||||
@@ -98,11 +100,12 @@ abstract class Picker
|
||||
if (empty($this->options['map']) === false) {
|
||||
$result[] = $this->options['map']($item);
|
||||
} else {
|
||||
$result[] = $item->panelPickerData([
|
||||
'image' => $this->options['image'],
|
||||
'info' => $this->options['info'],
|
||||
'model' => $this->options['model'],
|
||||
'text' => $this->options['text'],
|
||||
$result[] = $item->panel()->pickerData([
|
||||
'image' => $this->options['image'],
|
||||
'info' => $this->options['info'],
|
||||
'layout' => $this->options['layout'],
|
||||
'model' => $this->options['model'],
|
||||
'text' => $this->options['text'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ namespace Kirby\Cms;
|
||||
use Exception;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
/**
|
||||
* Represents a Plugin and handles parsing of
|
||||
@@ -45,8 +46,36 @@ class Plugin extends Model
|
||||
$this->setName($name);
|
||||
$this->extends = $extends;
|
||||
$this->root = $extends['root'] ?? dirname(debug_backtrace()[0]['file']);
|
||||
$this->info = empty($extends['info']) === false && is_array($extends['info']) ? $extends['info'] : null;
|
||||
|
||||
unset($this->extends['root']);
|
||||
unset($this->extends['root'], $this->extends['info']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array with author information
|
||||
* from the composer file
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function authors(): array
|
||||
{
|
||||
return $this->info()['authors'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma-separated list with all author names
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function authorsNames(): string
|
||||
{
|
||||
$names = [];
|
||||
|
||||
foreach ($this->authors() as $author) {
|
||||
$names[] = $author['name'] ?? null;
|
||||
}
|
||||
|
||||
return implode(', ', array_filter($names));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +86,16 @@ class Plugin extends Model
|
||||
return $this->extends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique id for the plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return $this->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -76,6 +115,22 @@ class Plugin extends Model
|
||||
return $this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to the plugin homepage
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function link(): ?string
|
||||
{
|
||||
$homepage = $this->info['homepage'] ?? null;
|
||||
$docs = $this->info['support']['docs'] ?? null;
|
||||
$source = $this->info['support']['source'] ?? null;
|
||||
|
||||
$link = $homepage ?? $docs ?? $source;
|
||||
|
||||
return V::url($link) ? $link : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -153,6 +208,14 @@ class Plugin extends Model
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->propertiesToArray();
|
||||
return [
|
||||
'authors' => $this->authors(),
|
||||
'description' => $this->description(),
|
||||
'name' => $this->name(),
|
||||
'license' => $this->license(),
|
||||
'link' => $this->link(),
|
||||
'root' => $this->root(),
|
||||
'version' => $this->version()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
/**
|
||||
* Plugin assets are automatically copied/linked
|
||||
@@ -66,15 +66,11 @@ class PluginAssets
|
||||
static::clean($pluginName);
|
||||
|
||||
$target = $plugin->mediaRoot() . '/' . $filename;
|
||||
$url = $plugin->mediaUrl() . '/' . $filename;
|
||||
|
||||
// create the plugin directory first
|
||||
Dir::make($plugin->mediaRoot(), true);
|
||||
|
||||
if (F::link($source, $target, 'symlink') === true) {
|
||||
return Response::redirect($url);
|
||||
}
|
||||
// create a symlink if possible
|
||||
F::link($source, $target, 'symlink');
|
||||
|
||||
// return the file response
|
||||
return Response::file($source);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\Mime;
|
||||
use Kirby\Filesystem\Mime;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
|
@@ -4,7 +4,7 @@ namespace Kirby\Cms;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
|
@@ -4,6 +4,8 @@ namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Panel\Site as Panel;
|
||||
use Kirby\Toolkit\A;
|
||||
|
||||
/**
|
||||
@@ -113,7 +115,7 @@ class Site extends ModelWithContent
|
||||
}
|
||||
|
||||
// return site content otherwise
|
||||
return $this->content()->get($method, $arguments);
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,31 +414,13 @@ class Site extends ModelWithContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
* Returns the panel info object
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @return \Kirby\Panel\Site
|
||||
*/
|
||||
public function panelPath(): string
|
||||
public function panel()
|
||||
{
|
||||
return 'site';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
if ($relative === true) {
|
||||
return '/' . $this->panelPath();
|
||||
} else {
|
||||
return $this->kirby()->url('panel') . '/' . $this->panelPath();
|
||||
}
|
||||
return new Panel($this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -571,7 +555,7 @@ class Site extends ModelWithContent
|
||||
* @param string|null $url
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUrl($url = null)
|
||||
protected function setUrl(?string $url = null)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
@@ -603,7 +587,7 @@ class Site extends ModelWithContent
|
||||
* @param string|null $language
|
||||
* @return string
|
||||
*/
|
||||
public function url($language = null): string
|
||||
public function url(?string $language = null): string
|
||||
{
|
||||
if ($language !== null || $this->kirby()->multilang() === true) {
|
||||
return $this->urlForLanguage($language);
|
||||
@@ -675,4 +659,41 @@ class Site extends ModelWithContent
|
||||
{
|
||||
return Dir::wasModifiedAfter($this->root(), $time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deprecated!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelPath(): string
|
||||
{
|
||||
return $this->panel()->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
return $this->panel()->url($relative);
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,10 @@ trait SiteActions
|
||||
*/
|
||||
public function changeTitle(string $title, string $languageCode = null)
|
||||
{
|
||||
$arguments = ['site' => $this, 'title' => $title, 'languageCode' => $languageCode];
|
||||
$site = $this;
|
||||
$title = trim($title);
|
||||
$arguments = compact('site', 'title', 'languageCode');
|
||||
|
||||
return $this->commit('changeTitle', $arguments, function ($site, $title, $languageCode) {
|
||||
return $site->save(['title' => $title], $languageCode);
|
||||
});
|
||||
|
@@ -3,11 +3,11 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
/**
|
||||
* The StructureObject reprents each item
|
||||
* The StructureObject represents each item
|
||||
* in a Structure collection. StructureObjects
|
||||
* behave pretty much the same as Pages or Users
|
||||
* and have a Content object to access their fields.
|
||||
* All fields in a StructureObject are therefor also
|
||||
* All fields in a StructureObject are therefore also
|
||||
* wrapped in a Field object and can be accessed in
|
||||
* the same way as Page fields. They also use the same
|
||||
* Field methods.
|
||||
@@ -61,7 +61,7 @@ class StructureObject extends Model
|
||||
return $this->$method;
|
||||
}
|
||||
|
||||
return $this->content()->get($method, $arguments);
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,10 +6,10 @@ use Kirby\Data\Json;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Remote;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
use Throwable;
|
||||
@@ -56,25 +56,6 @@ class System
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an status array of all checks
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function status(): array
|
||||
{
|
||||
return [
|
||||
'accounts' => $this->accounts(),
|
||||
'content' => $this->content(),
|
||||
'curl' => $this->curl(),
|
||||
'sessions' => $this->sessions(),
|
||||
'mbstring' => $this->mbstring(),
|
||||
'media' => $this->media(),
|
||||
'php' => $this->php(),
|
||||
'server' => $this->server(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a writable accounts folder
|
||||
*
|
||||
@@ -132,6 +113,13 @@ class System
|
||||
throw new PermissionException('The accounts directory could not be created');
|
||||
}
|
||||
|
||||
// init /site/sessions
|
||||
try {
|
||||
Dir::make($this->app->root('sessions'));
|
||||
} catch (Throwable $e) {
|
||||
throw new PermissionException('The sessions directory could not be created');
|
||||
}
|
||||
|
||||
// init /content
|
||||
try {
|
||||
Dir::make($this->app->root('content'));
|
||||
@@ -227,44 +215,6 @@ class System
|
||||
return in_array(false, array_values($this->status()), true) === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the app's index URL for
|
||||
* licensing purposes
|
||||
*
|
||||
* @param string|null $url Input URL, by default the app's index URL
|
||||
* @return string Normalized URL
|
||||
*/
|
||||
protected function licenseUrl(string $url = null): string
|
||||
{
|
||||
if ($url === null) {
|
||||
$url = $this->indexUrl();
|
||||
}
|
||||
|
||||
// remove common "testing" subdomains as well as www.
|
||||
// to ensure that installations of the same site have
|
||||
// the same license URL; only for installations at /,
|
||||
// subdirectory installations are difficult to normalize
|
||||
if (Str::contains($url, '/') === false) {
|
||||
if (Str::startsWith($url, 'www.')) {
|
||||
return substr($url, 4);
|
||||
}
|
||||
|
||||
if (Str::startsWith($url, 'dev.')) {
|
||||
return substr($url, 4);
|
||||
}
|
||||
|
||||
if (Str::startsWith($url, 'test.')) {
|
||||
return substr($url, 5);
|
||||
}
|
||||
|
||||
if (Str::startsWith($url, 'staging.')) {
|
||||
return substr($url, 8);
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the license file and returns
|
||||
* the license information if available
|
||||
@@ -276,7 +226,7 @@ class System
|
||||
public function license()
|
||||
{
|
||||
try {
|
||||
$license = Json::read($this->app->root('config') . '/.license');
|
||||
$license = Json::read($this->app->root('license'));
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
@@ -319,13 +269,51 @@ class System
|
||||
// only return the actual license key if the
|
||||
// current user has appropriate permissions
|
||||
$user = $this->app->user();
|
||||
if ($user && $user->role()->permissions()->for('access', 'settings') === true) {
|
||||
if ($user && $user->isAdmin() === true) {
|
||||
return $license['license'];
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the app's index URL for
|
||||
* licensing purposes
|
||||
*
|
||||
* @param string|null $url Input URL, by default the app's index URL
|
||||
* @return string Normalized URL
|
||||
*/
|
||||
protected function licenseUrl(string $url = null): string
|
||||
{
|
||||
if ($url === null) {
|
||||
$url = $this->indexUrl();
|
||||
}
|
||||
|
||||
// remove common "testing" subdomains as well as www.
|
||||
// to ensure that installations of the same site have
|
||||
// the same license URL; only for installations at /,
|
||||
// subdirectory installations are difficult to normalize
|
||||
if (Str::contains($url, '/') === false) {
|
||||
if (Str::startsWith($url, 'www.')) {
|
||||
return substr($url, 4);
|
||||
}
|
||||
|
||||
if (Str::startsWith($url, 'dev.')) {
|
||||
return substr($url, 4);
|
||||
}
|
||||
|
||||
if (Str::startsWith($url, 'test.')) {
|
||||
return substr($url, 5);
|
||||
}
|
||||
|
||||
if (Str::startsWith($url, 'staging.')) {
|
||||
return substr($url, 8);
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured UI modes for the login form
|
||||
* with their respective options
|
||||
@@ -416,10 +404,21 @@ class System
|
||||
public function php(): bool
|
||||
{
|
||||
return
|
||||
version_compare(PHP_VERSION, '7.3.0', '>=') === true &&
|
||||
version_compare(PHP_VERSION, '7.4.0', '>=') === true &&
|
||||
version_compare(PHP_VERSION, '8.1.0', '<') === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted collection of all
|
||||
* installed plugins
|
||||
*
|
||||
* @return \Kirby\Cms\Collection
|
||||
*/
|
||||
public function plugins()
|
||||
{
|
||||
return (new Collection(App::instance()->plugins()))->sortBy('name', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the license key
|
||||
* and adds it to the .license file in the config
|
||||
@@ -445,10 +444,11 @@ class System
|
||||
]);
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
$response = Remote::get('https://licenses.getkirby.com/register', [
|
||||
'data' => [
|
||||
'license' => $license,
|
||||
'email' => $email,
|
||||
'email' => Str::lower(trim($email)),
|
||||
'domain' => $this->indexUrl()
|
||||
]
|
||||
]);
|
||||
@@ -464,7 +464,7 @@ class System
|
||||
$json['email'] = $email;
|
||||
|
||||
// where to store the license file
|
||||
$file = $this->app->root('config') . '/.license';
|
||||
$file = $this->app->root('license');
|
||||
|
||||
// save the license information
|
||||
Json::write($file, $json);
|
||||
@@ -474,6 +474,7 @@ class System
|
||||
'key' => 'license.verification'
|
||||
]);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -484,6 +485,16 @@ class System
|
||||
* @return bool
|
||||
*/
|
||||
public function server(): bool
|
||||
{
|
||||
return $this->serverSoftware() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detected server software
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function serverSoftware(): ?string
|
||||
{
|
||||
if ($servers = $this->app->option('servers')) {
|
||||
$servers = A::wrap($servers);
|
||||
@@ -499,7 +510,9 @@ class System
|
||||
|
||||
$software = $_SERVER['SERVER_SOFTWARE'] ?? null;
|
||||
|
||||
return preg_match('!(' . implode('|', $servers) . ')!i', $software) > 0;
|
||||
preg_match('!(' . implode('|', $servers) . ')!i', $software, $matches);
|
||||
|
||||
return $matches[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -513,10 +526,45 @@ class System
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the status as array
|
||||
* Get an status array of all checks
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function status(): array
|
||||
{
|
||||
return [
|
||||
'accounts' => $this->accounts(),
|
||||
'content' => $this->content(),
|
||||
'curl' => $this->curl(),
|
||||
'sessions' => $this->sessions(),
|
||||
'mbstring' => $this->mbstring(),
|
||||
'media' => $this->media(),
|
||||
'php' => $this->php(),
|
||||
'server' => $this->server(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the site's title as defined in the
|
||||
* content file or `site.yml` blueprint
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
$site = $this->app->site();
|
||||
|
||||
if ($site->title()->isNotEmpty()) {
|
||||
return $site->title()->value();
|
||||
}
|
||||
|
||||
return $site->blueprint()->title();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->status();
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Tpl;
|
||||
|
||||
/**
|
||||
|
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* A collection of all available Translations.
|
||||
|
@@ -61,9 +61,6 @@ class Url extends BaseUrl
|
||||
public static function to(string $path = null, $options = null): string
|
||||
{
|
||||
$kirby = App::instance();
|
||||
|
||||
return ($kirby->component('url'))($kirby, $path, $options, function (string $path = null, $options = null) use ($kirby) {
|
||||
return ($kirby->nativeComponent('url'))($kirby, $path, $options);
|
||||
});
|
||||
return ($kirby->component('url'))($kirby, $path, $options);
|
||||
}
|
||||
}
|
||||
|
@@ -5,8 +5,9 @@ namespace Kirby\Cms;
|
||||
use Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Panel\User as Panel;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -115,7 +116,7 @@ class User extends ModelWithContent
|
||||
}
|
||||
|
||||
// return site content otherwise
|
||||
return $this->content()->get($method, $arguments);
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +126,8 @@ class User extends ModelWithContent
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$props['id'] = $props['id'] ?? $this->createId();
|
||||
// TODO: refactor later to avoid redundant prop setting
|
||||
$this->setProperty('id', $props['id'] ?? $this->createId(), true);
|
||||
$this->setProperties($props);
|
||||
}
|
||||
|
||||
@@ -578,92 +580,13 @@ class User extends ModelWithContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel icon definition
|
||||
* Returns the panel info object
|
||||
*
|
||||
* @internal
|
||||
* @param array $params
|
||||
* @return array
|
||||
* @return \Kirby\Panel\User
|
||||
*/
|
||||
public function panelIcon(array $params = null): array
|
||||
public function panel()
|
||||
{
|
||||
$params['type'] = 'user';
|
||||
|
||||
return parent::panelIcon($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Cms\Asset|null
|
||||
*/
|
||||
protected function panelImageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
return $this->avatar();
|
||||
}
|
||||
|
||||
return parent::panelImageSource($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function panelPath(): string
|
||||
{
|
||||
return 'users/' . $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns prepared data for the panel user picker
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function panelPickerData(array $params = null): array
|
||||
{
|
||||
$image = $this->panelImage($params['image'] ?? []);
|
||||
$icon = $this->panelIcon($image);
|
||||
|
||||
// escape the default text
|
||||
// TODO: no longer needed in 3.6
|
||||
$textQuery = $params['text'] ?? '{{ user.username }}';
|
||||
$text = $this->toString($textQuery);
|
||||
if ($textQuery === '{{ user.username }}') {
|
||||
$text = Escape::html($text);
|
||||
}
|
||||
|
||||
return [
|
||||
'icon' => $icon,
|
||||
'id' => $this->id(),
|
||||
'image' => $image,
|
||||
'email' => $this->email(),
|
||||
'info' => $this->toString($params['info'] ?? false),
|
||||
'link' => $this->panelUrl(true),
|
||||
'text' => $text,
|
||||
'username' => $this->username(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
if ($relative === true) {
|
||||
return '/' . $this->panelPath();
|
||||
} else {
|
||||
return $this->kirby()->url('panel') . '/' . $this->panelPath();
|
||||
}
|
||||
return new Panel($this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -909,13 +832,13 @@ class User extends ModelWithContent
|
||||
* @param string $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* @return string
|
||||
*/
|
||||
public function toString(string $template = null, array $data = [], string $fallback = ''): string
|
||||
public function toString(string $template = null, array $data = [], string $fallback = '', string $handler = 'template'): string
|
||||
{
|
||||
if ($template === null) {
|
||||
$template = $this->email();
|
||||
}
|
||||
|
||||
return parent::toString($template, $data);
|
||||
return parent::toString($template, $data, $fallback, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -951,9 +874,61 @@ class User extends ModelWithContent
|
||||
}
|
||||
|
||||
if (password_verify($password, $this->password()) !== true) {
|
||||
throw new InvalidArgumentException(['key' => 'user.password.wrong']);
|
||||
throw new InvalidArgumentException(['key' => 'user.password.wrong', 'httpCode' => 401]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deprecated!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelPath(): string
|
||||
{
|
||||
return $this->panel()->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns prepared data for the panel user picker
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelPickerData(array $params = null): array
|
||||
{
|
||||
return $this->panel()->pickerData($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @todo Add `deprecated()` helper warning in 3.7.0
|
||||
* @todo Remove in 3.8.0
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function panelUrl(bool $relative = false): string
|
||||
{
|
||||
return $this->panel()->url($relative);
|
||||
}
|
||||
}
|
||||
|
@@ -6,10 +6,12 @@ use Closure;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Http\Idn;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* UserActions
|
||||
@@ -30,6 +32,8 @@ trait UserActions
|
||||
*/
|
||||
public function changeEmail(string $email)
|
||||
{
|
||||
$email = trim($email);
|
||||
|
||||
return $this->commit('changeEmail', ['user' => $this, 'email' => Idn::decodeEmail($email)], function ($user, $email) {
|
||||
$user = $user->clone([
|
||||
'email' => $email
|
||||
@@ -39,6 +43,9 @@ trait UserActions
|
||||
'email' => $email
|
||||
]);
|
||||
|
||||
// update the users collection
|
||||
$user->kirby()->users()->set($user->id(), $user);
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
@@ -60,6 +67,9 @@ trait UserActions
|
||||
'language' => $language
|
||||
]);
|
||||
|
||||
// update the users collection
|
||||
$user->kirby()->users()->set($user->id(), $user);
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
@@ -72,6 +82,8 @@ trait UserActions
|
||||
*/
|
||||
public function changeName(string $name)
|
||||
{
|
||||
$name = trim($name);
|
||||
|
||||
return $this->commit('changeName', ['user' => $this, 'name' => $name], function ($user, $name) {
|
||||
$user = $user->clone([
|
||||
'name' => $name
|
||||
@@ -81,6 +93,9 @@ trait UserActions
|
||||
'name' => $name
|
||||
]);
|
||||
|
||||
// update the users collection
|
||||
$user->kirby()->users()->set($user->id(), $user);
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
@@ -100,6 +115,9 @@ trait UserActions
|
||||
|
||||
$user->writePassword($password);
|
||||
|
||||
// update the users collection
|
||||
$user->kirby()->users()->set($user->id(), $user);
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
@@ -121,6 +139,9 @@ trait UserActions
|
||||
'role' => $role
|
||||
]);
|
||||
|
||||
// update the users collection
|
||||
$user->kirby()->users()->set($user->id(), $user);
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
@@ -232,14 +253,21 @@ trait UserActions
|
||||
public function createId(): string
|
||||
{
|
||||
$length = 8;
|
||||
$id = Str::random($length);
|
||||
|
||||
while ($this->kirby()->users()->has($id)) {
|
||||
$length++;
|
||||
$id = Str::random($length);
|
||||
}
|
||||
do {
|
||||
try {
|
||||
$id = Str::random($length);
|
||||
if (UserRules::validId($this, $id) === true) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
return $id;
|
||||
// we can't really test for a random match
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Throwable $e) {
|
||||
$length++;
|
||||
}
|
||||
} while (true);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,6 +343,9 @@ trait UserActions
|
||||
$this->kirby()->auth()->setUser($user);
|
||||
}
|
||||
|
||||
// update the users collection
|
||||
$user->kirby()->users()->set($user->id(), $user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
@@ -327,6 +358,11 @@ trait UserActions
|
||||
*/
|
||||
protected function updateCredentials(array $credentials): bool
|
||||
{
|
||||
// normalize the email address
|
||||
if (isset($credentials['email']) === true) {
|
||||
$credentials['email'] = Str::lower(trim($credentials['email']));
|
||||
}
|
||||
|
||||
return $this->writeCredentials(array_merge($this->credentials(), $credentials));
|
||||
}
|
||||
|
||||
|
@@ -299,6 +299,10 @@ class UserRules
|
||||
*/
|
||||
public static function validId(User $user, string $id): bool
|
||||
{
|
||||
if ($id === 'account') {
|
||||
throw new InvalidArgumentException('"account" is a reserved word and cannot be used as user id');
|
||||
}
|
||||
|
||||
if ($user->kirby()->users()->find($id)) {
|
||||
throw new DuplicateException('A user with this id exists');
|
||||
}
|
||||
|
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -37,12 +38,13 @@ class Users extends Collection
|
||||
* an entire second collection to the
|
||||
* current collection
|
||||
*
|
||||
* @param mixed $object
|
||||
* @param \Kirby\Cms\Users|\Kirby\Cms\User|string $object
|
||||
* @return $this
|
||||
* @throws \Kirby\Exception\InvalidArgumentException When no `User` or `Users` object or an ID of an existing user is passed
|
||||
*/
|
||||
public function add($object)
|
||||
{
|
||||
// add a page collection
|
||||
// add a users collection
|
||||
if (is_a($object, self::class) === true) {
|
||||
$this->data = array_merge($this->data, $object->data);
|
||||
|
||||
@@ -53,6 +55,11 @@ class Users extends Collection
|
||||
// add a user object
|
||||
} elseif (is_a($object, 'Kirby\Cms\User') === true) {
|
||||
$this->__set($object->id(), $object);
|
||||
|
||||
// give a useful error message on invalid input;
|
||||
// silently ignore "empty" values for compatibility with existing setups
|
||||
} elseif (in_array($object, [null, false, true], true) !== true) {
|
||||
throw new InvalidArgumentException('You must pass a Users or User object or an ID of an existing user to the Users collection');
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace Kirby\Data;
|
||||
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* The `Data` class provides readers and
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace Kirby\Data;
|
||||
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* Base handler abstract,
|
||||
|
@@ -34,7 +34,7 @@ class Json extends Handler
|
||||
*/
|
||||
public static function decode($string): array
|
||||
{
|
||||
if ($string === null) {
|
||||
if ($string === null || $string === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@ namespace Kirby\Data;
|
||||
|
||||
use Kirby\Exception\BadMethodCallException;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* Reader and write of PHP files with data in a returned array
|
||||
@@ -49,10 +49,10 @@ class PHP extends Handler
|
||||
/**
|
||||
* PHP strings shouldn't be decoded manually
|
||||
*
|
||||
* @param mixed $array
|
||||
* @param mixed $string
|
||||
* @return array
|
||||
*/
|
||||
public static function decode($array): array
|
||||
public static function decode($string): array
|
||||
{
|
||||
throw new BadMethodCallException('The PHP::decode() method is not implemented');
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ class Txt extends Handler
|
||||
*/
|
||||
public static function decode($string): array
|
||||
{
|
||||
if ($string === null) {
|
||||
if ($string === null || $string === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@@ -35,7 +35,7 @@ class Xml extends Handler
|
||||
*/
|
||||
public static function decode($string): array
|
||||
{
|
||||
if ($string === null) {
|
||||
if ($string === null || $string === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@@ -50,7 +50,7 @@ class Yaml extends Handler
|
||||
*/
|
||||
public static function decode($string): array
|
||||
{
|
||||
if ($string === null) {
|
||||
if ($string === null || $string === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@@ -205,7 +205,7 @@ class Database
|
||||
}
|
||||
|
||||
// fetch the dsn and store it
|
||||
$this->dsn = static::$types[$this->type]['dsn']($options);
|
||||
$this->dsn = (static::$types[$this->type]['dsn'])($options);
|
||||
|
||||
// try to connect
|
||||
$this->connection = new PDO($this->dsn, $options['user'], $options['password']);
|
||||
@@ -504,7 +504,7 @@ class Database
|
||||
{
|
||||
if ($this->tables === null) {
|
||||
// Get the list of tables from the database
|
||||
$sql = $this->sql()->tables($this->database);
|
||||
$sql = $this->sql()->tables();
|
||||
$results = $this->query($sql['query'], $sql['bindings']);
|
||||
|
||||
if ($results) {
|
||||
|
@@ -123,7 +123,7 @@ class Db
|
||||
public static function __callStatic(string $method, $arguments)
|
||||
{
|
||||
if (isset(static::$queries[$method])) {
|
||||
return static::$queries[$method](...$arguments);
|
||||
return (static::$queries[$method])(...$arguments);
|
||||
}
|
||||
|
||||
if (static::$connection !== null && method_exists(static::$connection, $method) === true) {
|
||||
@@ -134,9 +134,10 @@ class Db
|
||||
}
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
/**
|
||||
* Shortcut for SELECT clauses
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param mixed $columns Either a string with columns or an array of column names
|
||||
@@ -152,7 +153,6 @@ Db::$queries['select'] = function (string $table, $columns = '*', $where = null,
|
||||
|
||||
/**
|
||||
* Shortcut for selecting a single row in a table
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param mixed $columns Either a string with columns or an array of column names
|
||||
@@ -168,7 +168,6 @@ Db::$queries['first'] = Db::$queries['row'] = Db::$queries['one'] = function (st
|
||||
|
||||
/**
|
||||
* Returns only values from a single column
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param string $column The name of the column to select from
|
||||
@@ -184,19 +183,17 @@ Db::$queries['column'] = function (string $table, string $column, $where = null,
|
||||
|
||||
/**
|
||||
* Shortcut for inserting a new row into a table
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param array $values An array of values which should be inserted
|
||||
* @return int ID of the inserted row
|
||||
* @return mixed Returns the last inserted id on success or false
|
||||
*/
|
||||
Db::$queries['insert'] = function (string $table, array $values): int {
|
||||
Db::$queries['insert'] = function (string $table, array $values) {
|
||||
return Db::table($table)->insert($values);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shortcut for updating a row in a table
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param array $values An array of values which should be inserted
|
||||
@@ -209,7 +206,6 @@ Db::$queries['update'] = function (string $table, array $values, $where = null):
|
||||
|
||||
/**
|
||||
* Shortcut for deleting rows in a table
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param mixed $where An optional WHERE clause
|
||||
@@ -221,7 +217,6 @@ Db::$queries['delete'] = function (string $table, $where = null): bool {
|
||||
|
||||
/**
|
||||
* Shortcut for counting rows in a table
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param mixed $where An optional WHERE clause
|
||||
@@ -233,7 +228,6 @@ Db::$queries['count'] = function (string $table, $where = null): int {
|
||||
|
||||
/**
|
||||
* Shortcut for calculating the minimum value in a column
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param string $column The name of the column of which the minimum should be calculated
|
||||
@@ -246,7 +240,6 @@ Db::$queries['min'] = function (string $table, string $column, $where = null): f
|
||||
|
||||
/**
|
||||
* Shortcut for calculating the maximum value in a column
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param string $column The name of the column of which the maximum should be calculated
|
||||
@@ -259,7 +252,6 @@ Db::$queries['max'] = function (string $table, string $column, $where = null): f
|
||||
|
||||
/**
|
||||
* Shortcut for calculating the average value in a column
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param string $column The name of the column of which the average should be calculated
|
||||
@@ -272,7 +264,6 @@ Db::$queries['avg'] = function (string $table, string $column, $where = null): f
|
||||
|
||||
/**
|
||||
* Shortcut for calculating the sum of all values in a column
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $table The name of the table which should be queried
|
||||
* @param string $column The name of the column of which the sum should be calculated
|
||||
@@ -282,3 +273,5 @@ Db::$queries['avg'] = function (string $table, string $column, $where = null): f
|
||||
Db::$queries['sum'] = function (string $table, string $column, $where = null): float {
|
||||
return Db::table($table)->where($where)->sum($column);
|
||||
};
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
@@ -341,7 +341,7 @@ class Query
|
||||
*/
|
||||
public function innerJoin($table, $on)
|
||||
{
|
||||
return $this->join($table, $on, 'inner');
|
||||
return $this->join($table, $on, 'inner join');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -710,7 +710,7 @@ class Query
|
||||
$this->database->fail();
|
||||
}
|
||||
|
||||
$result = $this->database->execute($sql['query'], $sql['bindings'], $params);
|
||||
$result = $this->database->execute($sql['query'], $sql['bindings']);
|
||||
|
||||
$this->reset();
|
||||
|
||||
@@ -1009,9 +1009,8 @@ class Query
|
||||
$key = $sql->columnName($this->table, $args[0]);
|
||||
|
||||
// ->where('username', 'in', ['myuser', 'myotheruser']);
|
||||
$predicate = trim(strtoupper($args[1]));
|
||||
if (is_array($args[2]) === true) {
|
||||
$predicate = trim(strtoupper($args[1]));
|
||||
|
||||
if (in_array($predicate, ['IN', 'NOT IN']) === false) {
|
||||
throw new InvalidArgumentException('Invalid predicate ' . $predicate);
|
||||
}
|
||||
@@ -1029,11 +1028,8 @@ class Query
|
||||
// add that to the where clause in parenthesis
|
||||
$result = $key . ' ' . $predicate . ' (' . implode(', ', $values) . ')';
|
||||
|
||||
$this->bindings($bindings);
|
||||
|
||||
// ->where('username', 'like', 'myuser');
|
||||
} else {
|
||||
$predicate = trim(strtoupper($args[1]));
|
||||
$predicates = [
|
||||
'=', '>=', '>', '<=', '<', '<>', '!=', '<=>',
|
||||
'IS', 'IS NOT',
|
||||
@@ -1051,9 +1047,8 @@ class Query
|
||||
$bindings[$valueBinding] = $args[2];
|
||||
|
||||
$result = $key . ' ' . $predicate . ' ' . $valueBinding;
|
||||
|
||||
$this->bindings($bindings);
|
||||
}
|
||||
$this->bindings($bindings);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@@ -225,7 +225,7 @@ abstract class Sql
|
||||
'null' => $null,
|
||||
'default' => $columnDefault['query'],
|
||||
'unique' => $uniqueColumn
|
||||
], ''));
|
||||
], ['fallback' => '']));
|
||||
|
||||
return [
|
||||
'query' => $query,
|
||||
|
@@ -36,7 +36,7 @@ class PHPMailer extends Email
|
||||
$mailer->addReplyTo($replyTo, $this->replyToName() ?? '');
|
||||
}
|
||||
|
||||
// add (multiple) recepient, CC & BCC addresses
|
||||
// add (multiple) recipient, CC & BCC addresses
|
||||
foreach ($this->to() as $email => $name) {
|
||||
$mailer->addAddress($email, $name ?? '');
|
||||
}
|
||||
|
@@ -121,7 +121,11 @@ class Exception extends \Exception
|
||||
}
|
||||
|
||||
// format message with passed data
|
||||
$message = Str::template($message, $this->data, '-', '{', '}');
|
||||
$message = Str::template($message, $this->data, [
|
||||
'fallback' => '-',
|
||||
'start' => '{',
|
||||
'end' => '}'
|
||||
]);
|
||||
|
||||
// handover to Exception parent class constructor
|
||||
parent::__construct($message, null, $args['previous'] ?? null);
|
||||
|
@@ -1,17 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
namespace Kirby\Filesystem;
|
||||
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Cms\FileModifications;
|
||||
|
||||
/**
|
||||
* Anything in your public path can be converted
|
||||
* to an Asset object to use the same handy file
|
||||
* methods and thumbnail generation as for any other
|
||||
* Kirby files. Pass a relative path to the Asset
|
||||
* object to create the asset.
|
||||
* methods as for any other Kirby files. Pass a
|
||||
* relative path to the class to create the asset.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @package Kirby Filesystem
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
@@ -19,36 +18,28 @@ use Kirby\Toolkit\Properties;
|
||||
*/
|
||||
class Asset
|
||||
{
|
||||
use FileFoundation;
|
||||
use IsFile;
|
||||
use FileModifications;
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* Relative file path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Creates a new Asset object
|
||||
* for the given path.
|
||||
* Creates a new Asset object for the given path.
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
$this->setPath(dirname($path));
|
||||
$this->setRoot($this->kirby()->root('index') . '/' . $path);
|
||||
$this->setUrl($this->kirby()->url('index') . '/' . $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alternative text for the asset
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function alt()
|
||||
{
|
||||
return null;
|
||||
$this->setProperties([
|
||||
'path' => dirname($path),
|
||||
'root' => $this->kirby()->root('index') . '/' . $path,
|
||||
'url' => $this->kirby()->url('index') . '/' . $path
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
namespace Kirby\Filesystem;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Page;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -12,7 +14,12 @@ use Throwable;
|
||||
* listing, moving, copying or
|
||||
* evaluating directories etc.
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* For the Cms, it includes methods,
|
||||
* that handle scanning directories
|
||||
* and converting the results into
|
||||
* children, files and other page stuff.
|
||||
*
|
||||
* @package Kirby Filesystem
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
@@ -37,6 +44,8 @@ class Dir
|
||||
'@eaDir'
|
||||
];
|
||||
|
||||
public static $numSeparator = '_';
|
||||
|
||||
/**
|
||||
* Copy the directory to a new destination
|
||||
*
|
||||
@@ -189,6 +198,171 @@ class Dir
|
||||
return is_writable($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the directory and analyzes files,
|
||||
* content, meta info and children. This is used
|
||||
* in `Kirby\Cms\Page`, `Kirby\Cms\Site` and
|
||||
* `Kirby\Cms\User` objects to fetch all
|
||||
* relevant information.
|
||||
*
|
||||
* 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 $contentIgnore = null, bool $multilang = false): array
|
||||
{
|
||||
$dir = realpath($dir);
|
||||
|
||||
$inventory = [
|
||||
'children' => [],
|
||||
'files' => [],
|
||||
'template' => 'default',
|
||||
];
|
||||
|
||||
if ($dir === false) {
|
||||
return $inventory;
|
||||
}
|
||||
|
||||
$items = static::read($dir, $contentIgnore);
|
||||
|
||||
// a temporary store for all content files
|
||||
$content = [];
|
||||
|
||||
// sort all items naturally to avoid sorting issues later
|
||||
natsort($items);
|
||||
|
||||
foreach ($items as $item) {
|
||||
|
||||
// ignore all items with a leading dot
|
||||
if (in_array(substr($item, 0, 1), ['.', '_']) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$root = $dir . '/' . $item;
|
||||
|
||||
if (is_dir($root) === true) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
$inventory['children'][] = [
|
||||
'dirname' => $item,
|
||||
'model' => null,
|
||||
'num' => $num,
|
||||
'root' => $root,
|
||||
'slug' => $slug,
|
||||
];
|
||||
} else {
|
||||
$extension = pathinfo($item, PATHINFO_EXTENSION);
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove the language codes from all content filenames
|
||||
if ($multilang === true) {
|
||||
foreach ($content as $key => $filename) {
|
||||
$content[$key] = pathinfo($filename, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
$content = array_unique($content);
|
||||
}
|
||||
|
||||
$inventory = static::inventoryContent($inventory, $content);
|
||||
$inventory = static::inventoryModels($inventory, $contentExtension, $multilang);
|
||||
|
||||
return $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 inventoryContent(array $inventory, array $content): array
|
||||
{
|
||||
|
||||
// filter meta files from the content file
|
||||
if (empty($content) === true) {
|
||||
$inventory['template'] = 'default';
|
||||
return $inventory;
|
||||
}
|
||||
|
||||
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
|
||||
$inventory['template'] = $contentName;
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
@@ -198,7 +372,7 @@ class Dir
|
||||
*/
|
||||
public static function link(string $source, string $link): bool
|
||||
{
|
||||
Dir::make(dirname($link), true);
|
||||
static::make(dirname($link), true);
|
||||
|
||||
if (is_dir($link) === true) {
|
||||
return true;
|
||||
@@ -384,27 +558,25 @@ class Dir
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the directory and all subfolders and files
|
||||
* Gets the size of the directory
|
||||
*
|
||||
* @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)
|
||||
public static function size(string $dir, bool $recursive = true)
|
||||
{
|
||||
if (is_dir($dir) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$size = 0;
|
||||
$items = static::read($dir);
|
||||
// Get size for all direct files
|
||||
$size = F::size(static::files($dir, null, true));
|
||||
|
||||
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);
|
||||
// if recursive, add sizes of all subdirectories
|
||||
if ($recursive === true) {
|
||||
foreach (static::dirs($dir, null, true) as $subdir) {
|
||||
$size += static::size($subdir);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
namespace Kirby\Filesystem;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
use ZipArchive;
|
||||
|
||||
@@ -12,7 +14,7 @@ use ZipArchive;
|
||||
* level, like creating, reading,
|
||||
* deleting, copying or validatings files.
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @package Kirby Filesystem
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
@@ -20,6 +22,9 @@ use ZipArchive;
|
||||
*/
|
||||
class F
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $types = [
|
||||
'archive' => [
|
||||
'gz',
|
||||
@@ -105,6 +110,9 @@ class F
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
/**
|
||||
@@ -465,10 +473,7 @@ class F
|
||||
return false;
|
||||
}
|
||||
|
||||
$stat = stat($file);
|
||||
$mtime = $stat['mtime'] ?? 0;
|
||||
$ctime = $stat['ctime'] ?? 0;
|
||||
$modified = max([$mtime, $ctime]);
|
||||
$modified = filemtime($file);
|
||||
|
||||
if (is_null($format) === true) {
|
||||
return $modified;
|
||||
@@ -523,7 +528,7 @@ class F
|
||||
/**
|
||||
* Converts an integer size into a human readable format
|
||||
*
|
||||
* @param mixed $size The file size or a file path
|
||||
* @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
|
||||
@@ -532,7 +537,7 @@ class F
|
||||
public static function niceSize($size, $locale = null): string
|
||||
{
|
||||
// file mode
|
||||
if (is_string($size) === true && file_exists($size) === true) {
|
||||
if (is_string($size) === true || is_array($size) === true) {
|
||||
$size = static::size($size);
|
||||
}
|
||||
|
||||
@@ -638,6 +643,8 @@ class F
|
||||
* Returns the relative path of the file
|
||||
* starting after $in
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CountInLoopExpression)
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $in
|
||||
* @return string
|
||||
@@ -652,11 +659,24 @@ class F
|
||||
$file = str_replace('\\', '/', $file);
|
||||
$in = str_replace('\\', '/', $in);
|
||||
|
||||
if (Str::contains($file, $in) === false) {
|
||||
return basename($file);
|
||||
// trim trailing slashes
|
||||
$file = rtrim($file, '/');
|
||||
$in = rtrim($in, '/');
|
||||
|
||||
if (Str::contains($file, $in . '/') === false) {
|
||||
// make the paths relative by stripping what they have
|
||||
// in common and adding `../` tokens at the start
|
||||
$fileParts = explode('/', $file);
|
||||
$inParts = explode('/', $in);
|
||||
while (count($fileParts) && count($inParts) && ($fileParts[0] === $inParts[0])) {
|
||||
array_shift($fileParts);
|
||||
array_shift($inParts);
|
||||
}
|
||||
|
||||
return str_repeat('../', count($inParts)) . implode('/', $fileParts);
|
||||
}
|
||||
|
||||
return Str::after($file, $in);
|
||||
return '/' . Str::after($file, $in . '/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -696,8 +716,8 @@ class F
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
* $safe = f::safeName('über genious.txt');
|
||||
* // safe will be ueber-genious.txt
|
||||
* $safe = f::safeName('über genius.txt');
|
||||
* // safe will be ueber-genius.txt
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
@@ -732,13 +752,19 @@ class F
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of a file.
|
||||
* Returns the size of a file or an array of files.
|
||||
*
|
||||
* @param mixed $file The path
|
||||
* @param string|array $file file path or array of paths
|
||||
* @return int
|
||||
*/
|
||||
public static function size(string $file): int
|
||||
public static function size($file): int
|
||||
{
|
||||
if (is_array($file) === true) {
|
||||
return array_reduce($file, function ($total, $file) {
|
||||
return $total + F::size($file);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
try {
|
||||
return filesize($file);
|
||||
} catch (Throwable $e) {
|
||||
@@ -838,7 +864,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 exisiting file if available. false: overwrite.
|
||||
* @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
|
629
kirby/src/Filesystem/File.php
Executable file
629
kirby/src/Filesystem/File.php
Executable file
@@ -0,0 +1,629 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Filesystem;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Sane\Sane;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\Html;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
/**
|
||||
* Flexible File object with a set of helpful
|
||||
* methods to inspect and work with files.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @package Kirby Filesystem
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class File
|
||||
{
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* Absolute file path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Absolute file URL
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* Validation rules to be used for `::match()`
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $validations = [
|
||||
'maxsize' => ['size', 'max'],
|
||||
'minsize' => ['size', 'min']
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor sets all file properties
|
||||
*
|
||||
* @param array|string|null $props Properties or deprecated `$root` string
|
||||
* @param string|null $url Deprecated argument, use `$props['url']` instead
|
||||
*/
|
||||
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,
|
||||
'url' => $url
|
||||
];
|
||||
}
|
||||
|
||||
$this->setProperties($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL for the file object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->url() ?? $this->root() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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($filename = null): string
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an appropriate header for the asset
|
||||
*
|
||||
* @param bool $send
|
||||
* @return \Kirby\Http\Response|void
|
||||
*/
|
||||
public function header(bool $send = true)
|
||||
{
|
||||
$response = new Response('', $this->mime());
|
||||
|
||||
if ($send !== true) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file to html
|
||||
*
|
||||
* @param array $attr
|
||||
* @return string
|
||||
*/
|
||||
public function html(array $attr = []): string
|
||||
{
|
||||
return Html::a($this->url() ?? '', $attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 a resizable image
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isResizable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a preview can be displayed for the file
|
||||
* in the panel or in the frontend
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isViewable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file is writable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return F::isWritable($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app instance if it exists
|
||||
*
|
||||
* @return \Kirby\Cms\App|null
|
||||
*/
|
||||
public function kirby()
|
||||
{
|
||||
return App::instance(null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$rules = array_change_key_case($rules);
|
||||
|
||||
if (is_array($rules['mime'] ?? null) === true) {
|
||||
$mime = $this->mime();
|
||||
|
||||
// determine if any pattern matches the MIME type;
|
||||
// once any pattern matches, `$carry` is `true` and the rest is skipped
|
||||
$matches = array_reduce($rules['mime'], function ($carry, $pattern) use ($mime) {
|
||||
return $carry || Mime::matches($mime, $pattern);
|
||||
}, false);
|
||||
|
||||
if ($matches !== true) {
|
||||
throw new Exception([
|
||||
'key' => 'file.mime.invalid',
|
||||
'data' => compact('mime')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($rules['extension'] ?? null) === true) {
|
||||
$extension = $this->extension();
|
||||
if (in_array($extension, $rules['extension']) !== true) {
|
||||
throw new Exception([
|
||||
'key' => 'file.extension.invalid',
|
||||
'data' => compact('extension')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($rules['type'] ?? null) === true) {
|
||||
$type = $this->type();
|
||||
if (in_array($type, $rules['type']) !== true) {
|
||||
throw new Exception([
|
||||
'key' => 'file.type.invalid',
|
||||
'data' => compact('type')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (static::$validations as $key => $arguments) {
|
||||
$rule = $rules[$key] ?? null;
|
||||
|
||||
if ($rule !== null) {
|
||||
$property = $arguments[0];
|
||||
$validator = $arguments[1];
|
||||
|
||||
if (V::$validator($this->$property(), $rule) === false) {
|
||||
throw new Exception([
|
||||
'key' => 'file.' . $key,
|
||||
'data' => [$property => $rule]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the mime type of the file
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function mime()
|
||||
{
|
||||
return Mime::type($this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file's last modification time
|
||||
*
|
||||
* @param string $format
|
||||
* @param string|null $handler date or strftime
|
||||
* @return mixed
|
||||
*/
|
||||
public function modified(?string $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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the file contents depending on the file type
|
||||
* by overwriting the file with the sanitized version
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @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($typeLazy = false): void
|
||||
{
|
||||
Sane::sanitizeFile($this->root(), $typeLazy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sha1 hash of the file
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sha1(): string
|
||||
{
|
||||
return sha1_file($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 [
|
||||
'extension' => $this->extension(),
|
||||
'filename' => $this->filename(),
|
||||
'hash' => $this->hash(),
|
||||
'isReadable' => $this->isReadable(),
|
||||
'isResizable' => $this->isResizable(),
|
||||
'isWritable' => $this->isWritable(),
|
||||
'mime' => $this->mime(),
|
||||
'modified' => $this->modified('c'),
|
||||
'name' => $this->name(),
|
||||
'niceSize' => $this->niceSize(),
|
||||
'root' => $this->root(),
|
||||
'safeName' => F::safeName($this->name()),
|
||||
'size' => $this->size(),
|
||||
'type' => $this->type(),
|
||||
'url' => $this->url()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the entire file array into
|
||||
* a json string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson(): string
|
||||
{
|
||||
return json_encode($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file type.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function type(): ?string
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
namespace Kirby\Filesystem;
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The Filename class handles complex
|
||||
* The `Filename` class handles complex
|
||||
* mapping of file attributes (i.e for thumbnails)
|
||||
* into human readable filenames.
|
||||
*
|
||||
@@ -20,7 +20,7 @@ use Kirby\Toolkit\Str;
|
||||
* echo $filename->toString();
|
||||
* // result: some-file-300x200-crop-top-left-q80.jpg
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @package Kirby Filesystem
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
@@ -75,7 +75,10 @@ class Filename
|
||||
$this->filename = $filename;
|
||||
$this->template = $template;
|
||||
$this->attributes = $attributes;
|
||||
$this->extension = $this->sanitizeExtension(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
$this->extension = $this->sanitizeExtension(
|
||||
$attributes['format'] ??
|
||||
pathinfo($filename, PATHINFO_EXTENSION)
|
||||
);
|
||||
$this->name = $this->sanitizeName(pathinfo($filename, PATHINFO_FILENAME));
|
||||
}
|
||||
|
||||
@@ -136,7 +139,7 @@ class Filename
|
||||
$result[] = $value;
|
||||
break;
|
||||
case 'crop':
|
||||
$result[] = ($value === 'center') ? null : $key . '-' . $value;
|
||||
$result[] = ($value === 'center') ? 'crop' : $key . '-' . $value;
|
||||
break;
|
||||
default:
|
||||
$result[] = $key . $value;
|
||||
@@ -298,6 +301,6 @@ class Filename
|
||||
'name' => $this->name(),
|
||||
'attributes' => $this->attributesToString('-'),
|
||||
'extension' => $this->extension()
|
||||
], '');
|
||||
], ['fallback' => '']);
|
||||
}
|
||||
}
|
196
kirby/src/Filesystem/IsFile.php
Executable file
196
kirby/src/Filesystem/IsFile.php
Executable file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
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.
|
||||
* Adds `::asset()` method which returns either a
|
||||
* `Kirby\Filesystem\File` or `Kirby\Image\Image` object.
|
||||
* Proxies method calls to this object.
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @package Kirby Filesystem
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
trait IsFile
|
||||
{
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* File asset object
|
||||
*
|
||||
* @var \Kirby\Filesystem\File
|
||||
*/
|
||||
protected $asset;
|
||||
|
||||
/**
|
||||
* Absolute file path
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Absolute file URL
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* Constructor sets all file properties
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$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 = [])
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
throw new BadMethodCallException('The method: "' . $method . '" does not exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the asset to a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string)$this->asset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file asset object
|
||||
*
|
||||
* @param array|string|null $props
|
||||
* @return \Kirby\Filesystem\File
|
||||
*/
|
||||
public function asset($props = null)
|
||||
{
|
||||
if ($this->asset !== null) {
|
||||
return $this->asset;
|
||||
}
|
||||
|
||||
$props = $props ?? [
|
||||
'root' => $this->root(),
|
||||
'url' => $this->url()
|
||||
];
|
||||
|
||||
switch ($this->type()) {
|
||||
case 'image':
|
||||
return $this->asset = new Image($props);
|
||||
default:
|
||||
return $this->asset = new File($props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file exists on disk
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
// Important to include this in the trait
|
||||
// to avoid infinite loops when trying
|
||||
// to proxy the method from the asset object
|
||||
return file_exists($this->root()) === true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the app instance
|
||||
*
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
public function kirby()
|
||||
{
|
||||
return App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given file path
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function root(): ?string
|
||||
{
|
||||
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 file type
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function type(): ?string
|
||||
{
|
||||
// Important to include this in the trait
|
||||
// to avoid infinite loops when trying
|
||||
// to proxy the method from the asset object
|
||||
return F::type($this->root() ?? $this->url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute url for the file
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function url(): ?string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
namespace Kirby\Filesystem;
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
@@ -10,7 +11,7 @@ use SimpleXMLElement;
|
||||
* from different criteria like
|
||||
* extensions etc.
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @package Kirby Filesystem
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
@@ -127,12 +128,12 @@ class Mime
|
||||
// fixing map
|
||||
$map = [
|
||||
'text/html' => [
|
||||
'svg' => ['Kirby\Toolkit\Mime', 'fromSvg'],
|
||||
'svg' => ['Kirby\Filesystem\Mime', 'fromSvg'],
|
||||
],
|
||||
'text/plain' => [
|
||||
'css' => 'text/css',
|
||||
'json' => 'application/json',
|
||||
'svg' => ['Kirby\Toolkit\Mime', 'fromSvg'],
|
||||
'svg' => ['Kirby\Filesystem\Mime', 'fromSvg'],
|
||||
],
|
||||
'text/x-asm' => [
|
||||
'css' => 'text/css'
|
||||
@@ -161,7 +162,7 @@ class Mime
|
||||
* @param string $extension
|
||||
* @return string|null
|
||||
*/
|
||||
public static function fromExtension(string $extension)
|
||||
public static function fromExtension(string $extension): ?string
|
||||
{
|
||||
$mime = static::$types[$extension] ?? null;
|
||||
return is_array($mime) === true ? array_shift($mime) : $mime;
|
||||
@@ -295,7 +296,6 @@ class Mime
|
||||
|
||||
if ($value === $mime) {
|
||||
$extensions[] = $key;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@@ -238,7 +238,7 @@ class Field extends Component
|
||||
'help' => function () {
|
||||
/** @var \Kirby\Form\Field $this */
|
||||
if ($this->help) {
|
||||
$help = $this->model()->toString($this->help);
|
||||
$help = $this->model()->toSafeString($this->help);
|
||||
$help = $this->kirby()->kirbytext($help);
|
||||
return $help;
|
||||
}
|
||||
@@ -441,7 +441,7 @@ class Field extends Component
|
||||
ksort($array);
|
||||
|
||||
return array_filter($array, function ($item) {
|
||||
return $item !== null;
|
||||
return $item !== null && is_object($item) === false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -5,10 +5,10 @@ namespace Kirby\Form\Field;
|
||||
use Kirby\Cms\Block;
|
||||
use Kirby\Cms\Blocks as BlocksCollection;
|
||||
use Kirby\Cms\Fieldsets;
|
||||
use Kirby\Cms\Form;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Form\FieldClass;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Form\Mixin\EmptyState;
|
||||
use Kirby\Form\Mixin\Max;
|
||||
use Kirby\Form\Mixin\Min;
|
||||
@@ -146,6 +146,15 @@ class BlocksField extends FieldClass
|
||||
return ['uuid' => uuid()];
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'paste',
|
||||
'method' => 'POST',
|
||||
'action' => function () use ($field) {
|
||||
$value = BlocksCollection::parse(get('html'));
|
||||
$blocks = BlocksCollection::factory($value);
|
||||
return $field->blocksToValues($blocks->toArray());
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'fieldsets/(:any)',
|
||||
'method' => 'GET',
|
||||
|
@@ -4,10 +4,10 @@ namespace Kirby\Form\Field;
|
||||
|
||||
use Kirby\Cms\Blueprint;
|
||||
use Kirby\Cms\Fieldset;
|
||||
use Kirby\Cms\Form;
|
||||
use Kirby\Cms\Layout;
|
||||
use Kirby\Cms\Layouts;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
|
@@ -10,32 +10,116 @@ use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Abstract field class to be used instead
|
||||
* of functional field components for more
|
||||
* control.
|
||||
*
|
||||
* @package Kirby Form
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
abstract class FieldClass
|
||||
{
|
||||
use HasSiblings;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $after;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $autofocus;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $before;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $default;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $disabled;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $help;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $icon;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\ModelWithContent
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $placeholder;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $required;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Form\Fields
|
||||
*/
|
||||
protected $siblings;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $translate;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected $when;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* @param string $param
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $param, array $args)
|
||||
{
|
||||
if (isset($this->$param) === true) {
|
||||
@@ -45,6 +129,9 @@ abstract class FieldClass
|
||||
return $this->params[$param] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
$this->params = $params;
|
||||
@@ -71,28 +158,41 @@ abstract class FieldClass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function after(): ?string
|
||||
{
|
||||
return $this->stringTemplate($this->after);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function api(): array
|
||||
{
|
||||
return $this->routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function autofocus(): bool
|
||||
{
|
||||
return $this->autofocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function before(): ?string
|
||||
{
|
||||
return $this->stringTemplate($this->before);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @deprecated 3.5.0
|
||||
* @todo remove when the general field class setup has been refactored
|
||||
*
|
||||
* Returns the field data
|
||||
* in a format to be stored
|
||||
@@ -109,13 +209,11 @@ abstract class FieldClass
|
||||
/**
|
||||
* Returns the default value for the field,
|
||||
* which will be used when a page/file/user is created
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function default()
|
||||
{
|
||||
if ($this->default === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_string($this->default) === false) {
|
||||
return $this->default;
|
||||
}
|
||||
@@ -125,89 +223,14 @@ abstract class FieldClass
|
||||
|
||||
/**
|
||||
* If `true`, the field is no longer editable and will not be saved
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disabled(): bool
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional help text below the field
|
||||
*/
|
||||
public function help(): ?string
|
||||
{
|
||||
if (empty($this->help) === false) {
|
||||
$help = $this->stringTemplate($this->help);
|
||||
$help = $this->kirby()->kirbytext($help);
|
||||
return $help;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function i18n($param)
|
||||
{
|
||||
return empty($param) === false ? I18n::translate($param, $param) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional icon that will be shown at the end of the field
|
||||
*/
|
||||
public function icon(): ?string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function id(): string
|
||||
{
|
||||
return $this->name();
|
||||
}
|
||||
|
||||
public function isDisabled(): bool
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->isEmptyValue($this->value());
|
||||
}
|
||||
|
||||
public function isEmptyValue($value): bool
|
||||
{
|
||||
return in_array($value, [null, '', []], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the field is invalid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInvalid(): bool
|
||||
{
|
||||
return $this->isValid() === false;
|
||||
}
|
||||
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
public function isSaveable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the field is valid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(): bool
|
||||
{
|
||||
return empty($this->errors()) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all validations and returns an array of
|
||||
* error messages
|
||||
@@ -230,6 +253,110 @@ abstract class FieldClass
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional help text below the field
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function help(): ?string
|
||||
{
|
||||
if (empty($this->help) === false) {
|
||||
$help = $this->stringTemplate($this->help);
|
||||
$help = $this->kirby()->kirbytext($help);
|
||||
return $help;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array|null $param
|
||||
* @return string|null
|
||||
*/
|
||||
protected function i18n($param = null): ?string
|
||||
{
|
||||
return empty($param) === false ? I18n::translate($param, $param) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional icon that will be shown at the end of the field
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function icon(): ?string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return $this->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDisabled(): bool
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->isEmptyValue($this->value());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmptyValue($value = null): bool
|
||||
{
|
||||
return in_array($value, [null, '', []], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the field is invalid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInvalid(): bool
|
||||
{
|
||||
return $this->isValid() === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSaveable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the field is valid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(): bool
|
||||
{
|
||||
return empty($this->errors()) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kirby instance
|
||||
*
|
||||
@@ -242,6 +369,8 @@ abstract class FieldClass
|
||||
|
||||
/**
|
||||
* The field label can be set as string or associative array with translations
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
@@ -251,7 +380,7 @@ abstract class FieldClass
|
||||
/**
|
||||
* Returns the parent model
|
||||
*
|
||||
* @return mixed|null
|
||||
* @return mixed
|
||||
*/
|
||||
public function model()
|
||||
{
|
||||
@@ -324,6 +453,8 @@ abstract class FieldClass
|
||||
|
||||
/**
|
||||
* Optional placeholder value that will be shown when the field is empty
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function placeholder(): ?string
|
||||
{
|
||||
@@ -379,8 +510,8 @@ abstract class FieldClass
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @deprecated 3.5.0
|
||||
* @todo remove when the general field class setup has been refactored
|
||||
* @return bool
|
||||
*/
|
||||
public function save()
|
||||
@@ -388,91 +519,170 @@ abstract class FieldClass
|
||||
return $this->isSaveable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|null $after
|
||||
* @return void
|
||||
*/
|
||||
protected function setAfter($after = null)
|
||||
{
|
||||
$this->after = $this->i18n($after);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $autofocus
|
||||
* @return void
|
||||
*/
|
||||
protected function setAutofocus(bool $autofocus = false)
|
||||
{
|
||||
$this->autofocus = $autofocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|null $before
|
||||
* @return void
|
||||
*/
|
||||
protected function setBefore($before = null)
|
||||
{
|
||||
$this->before = $this->i18n($before);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $default
|
||||
* @return void
|
||||
*/
|
||||
protected function setDefault($default = null)
|
||||
{
|
||||
$this->default = $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $disabled
|
||||
* @return void
|
||||
*/
|
||||
protected function setDisabled(bool $disabled = false)
|
||||
{
|
||||
$this->disabled = $disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|null $help
|
||||
* @return void
|
||||
*/
|
||||
protected function setHelp($help = null)
|
||||
{
|
||||
$this->help = $this->i18n($help);
|
||||
}
|
||||
|
||||
protected function setIcon(string $icon = null)
|
||||
/**
|
||||
* @param string|null $icon
|
||||
* @return void
|
||||
*/
|
||||
protected function setIcon(?string $icon = null)
|
||||
{
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|null $label
|
||||
* @return void
|
||||
*/
|
||||
protected function setLabel($label = null)
|
||||
{
|
||||
$this->label = $this->i18n($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @return void
|
||||
*/
|
||||
protected function setModel(ModelWithContent $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
protected function setName(string $name = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|null $placeholder
|
||||
* @return void
|
||||
*/
|
||||
protected function setPlaceholder($placeholder = null)
|
||||
{
|
||||
$this->placeholder = $this->i18n($placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $required
|
||||
* @return void
|
||||
*/
|
||||
protected function setRequired(bool $required = false)
|
||||
{
|
||||
$this->required = $required;
|
||||
}
|
||||
|
||||
protected function setSiblings(Fields $siblings = null)
|
||||
/**
|
||||
* @param \Kirby\Form\Fields|null $siblings
|
||||
* @return void
|
||||
*/
|
||||
protected function setSiblings(?Fields $siblings = null)
|
||||
{
|
||||
$this->siblings = $siblings ?? new Fields([]);
|
||||
$this->siblings = $siblings ?? new Fields([$this]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $translate
|
||||
* @return void
|
||||
*/
|
||||
protected function setTranslate(bool $translate = true)
|
||||
{
|
||||
$this->translate = $translate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the when condition
|
||||
*
|
||||
* @param mixed $when
|
||||
* @return void
|
||||
*/
|
||||
protected function setWhen($when = null)
|
||||
{
|
||||
$this->when = $when;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the field width
|
||||
*
|
||||
* @param string|null $width
|
||||
* @return void
|
||||
*/
|
||||
protected function setWidth(string $width = null)
|
||||
{
|
||||
$this->width = $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all sibling fields
|
||||
*
|
||||
* @return \Kirby\Form\Fields
|
||||
*/
|
||||
protected function siblingsCollection()
|
||||
{
|
||||
return $this->siblings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string template in the given value
|
||||
*
|
||||
* @param string|null $string
|
||||
* @return string|null
|
||||
*/
|
||||
protected function stringTemplate(?string $string = null): ?string
|
||||
{
|
||||
if ($string !== null) {
|
||||
@@ -482,6 +692,13 @@ abstract class FieldClass
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value to a value
|
||||
* that can be stored in the text file
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function store($value)
|
||||
{
|
||||
return $value;
|
||||
@@ -514,6 +731,11 @@ abstract class FieldClass
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
return lcfirst(basename(str_replace(['\\', 'Field'], ['/', ''], static::class)));
|
||||
@@ -587,6 +809,10 @@ abstract class FieldClass
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
protected function valueFromJson($value): array
|
||||
{
|
||||
try {
|
||||
@@ -596,11 +822,20 @@ abstract class FieldClass
|
||||
}
|
||||
}
|
||||
|
||||
protected function valueFromYaml($value)
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
protected function valueFromYaml($value): array
|
||||
{
|
||||
return Data::decode($value, 'yaml');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $value
|
||||
* @param bool $pretty
|
||||
* @return string
|
||||
*/
|
||||
protected function valueToJson(array $value = null, bool $pretty = false): string
|
||||
{
|
||||
if ($pretty === true) {
|
||||
@@ -610,6 +845,10 @@ abstract class FieldClass
|
||||
return json_encode($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $value
|
||||
* @return string
|
||||
*/
|
||||
protected function valueToYaml(array $value = null): string
|
||||
{
|
||||
return Data::encode($value, 'yaml');
|
||||
|
@@ -3,7 +3,10 @@
|
||||
namespace Kirby\Form;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Model;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -54,6 +57,12 @@ class Form
|
||||
$strict = $props['strict'] ?? false;
|
||||
$inject = $props;
|
||||
|
||||
// prepare field properties for multilang setups
|
||||
$fields = static::prepareFieldsForLanguage(
|
||||
$fields,
|
||||
$props['language'] ?? null
|
||||
);
|
||||
|
||||
// lowercase all value names
|
||||
$values = array_change_key_case($values);
|
||||
$input = array_change_key_case($input);
|
||||
@@ -194,6 +203,42 @@ class Form
|
||||
return Field::factory('info', $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field object by name
|
||||
* and handle nested fields correctly
|
||||
*
|
||||
* @param string $name
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @return \Kirby\Form\Field
|
||||
*/
|
||||
public function field(string $name)
|
||||
{
|
||||
$form = $this;
|
||||
$fieldNames = Str::split($name, '+');
|
||||
$index = 0;
|
||||
$count = count($fieldNames);
|
||||
$field = null;
|
||||
|
||||
foreach ($fieldNames as $fieldName) {
|
||||
$index++;
|
||||
|
||||
if ($field = $form->fields()->get($fieldName)) {
|
||||
if ($count !== $index) {
|
||||
$form = $field->form();
|
||||
}
|
||||
} else {
|
||||
throw new NotFoundException('The field "' . $fieldName . '" could not be found');
|
||||
}
|
||||
}
|
||||
|
||||
// it can get this error only if $name is an empty string as $name = ''
|
||||
if ($field === null) {
|
||||
throw new NotFoundException('No field could be loaded');
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns form fields
|
||||
*
|
||||
@@ -204,6 +249,47 @@ class Form
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\Model $model
|
||||
* @param array $props
|
||||
* @return static
|
||||
*/
|
||||
public static function for(Model $model, array $props = [])
|
||||
{
|
||||
// get the original model data
|
||||
$original = $model->content($props['language'] ?? null)->toArray();
|
||||
$values = $props['values'] ?? [];
|
||||
|
||||
// convert closures to values
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_a($value, 'Closure') === true) {
|
||||
$values[$key] = $value($original[$key] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
// set a few defaults
|
||||
$props['values'] = array_merge($original, $values);
|
||||
$props['fields'] = $props['fields'] ?? [];
|
||||
$props['model'] = $model;
|
||||
|
||||
// search for the blueprint
|
||||
if (method_exists($model, 'blueprint') === true && $blueprint = $model->blueprint()) {
|
||||
$props['fields'] = $blueprint->fields();
|
||||
}
|
||||
|
||||
$ignoreDisabled = $props['ignoreDisabled'] ?? false;
|
||||
|
||||
// REFACTOR: this could be more elegant
|
||||
if ($ignoreDisabled === true) {
|
||||
$props['fields'] = array_map(function ($field) {
|
||||
$field['disabled'] = false;
|
||||
return $field;
|
||||
}, $props['fields']);
|
||||
}
|
||||
|
||||
return new static($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the form is invalid
|
||||
*
|
||||
@@ -224,6 +310,40 @@ class Form
|
||||
return empty($this->errors()) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables fields in secondary languages when
|
||||
* they are configured to be untranslatable
|
||||
*
|
||||
* @param array $fields
|
||||
* @param string|null $language
|
||||
* @return array
|
||||
*/
|
||||
protected static function prepareFieldsForLanguage(array $fields, ?string $language = null): array
|
||||
{
|
||||
$kirby = App::instance(null, true);
|
||||
|
||||
// only modify the fields if we have a valid Kirby multilang instance
|
||||
if (!$kirby || $kirby->multilang() === false) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
if ($language === null) {
|
||||
$language = $kirby->language()->code();
|
||||
}
|
||||
|
||||
if ($language !== $kirby->defaultLanguage()->code()) {
|
||||
foreach ($fields as $fieldName => $fieldProps) {
|
||||
// switch untranslatable fields to readonly
|
||||
if (($fieldProps['translate'] ?? true) === false) {
|
||||
$fields[$fieldName]['unset'] = true;
|
||||
$fields[$fieldName]['disabled'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the data of fields to strings
|
||||
*
|
||||
|
@@ -7,7 +7,6 @@ use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Http\Remote;
|
||||
use Kirby\Http\Url;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Toolkit\Query;
|
||||
use Kirby\Toolkit\Str;
|
||||
@@ -27,17 +26,17 @@ class OptionsApi
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* @var
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var
|
||||
* @var string|null
|
||||
*/
|
||||
protected $fetch;
|
||||
|
||||
/**
|
||||
* @var
|
||||
* @var array|string|null
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
@@ -47,7 +46,7 @@ class OptionsApi
|
||||
protected $text = '{{ item.value }}';
|
||||
|
||||
/**
|
||||
* @var
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
@@ -90,11 +89,7 @@ class OptionsApi
|
||||
protected function field(string $field, array $data): string
|
||||
{
|
||||
$value = $this->$field();
|
||||
return Str::template($value, $data, [
|
||||
'callback' => function ($result) {
|
||||
return Escape::html($result);
|
||||
}
|
||||
]);
|
||||
return Str::safeTemplate($value, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,14 +161,14 @@ class OptionsApi
|
||||
* @param string|null $fetch
|
||||
* @return $this
|
||||
*/
|
||||
protected function setFetch(string $fetch = null)
|
||||
protected function setFetch(?string $fetch = null)
|
||||
{
|
||||
$this->fetch = $fetch;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $options
|
||||
* @param array|string|null $options
|
||||
* @return $this
|
||||
*/
|
||||
protected function setOptions($options = null)
|
||||
@@ -183,30 +178,30 @@ class OptionsApi
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $text
|
||||
* @param string $text
|
||||
* @return $this
|
||||
*/
|
||||
protected function setText($text = null)
|
||||
protected function setText(?string $text = null)
|
||||
{
|
||||
$this->text = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUrl($url)
|
||||
protected function setUrl(string $url)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
protected function setValue($value = null)
|
||||
protected function setValue(?string $value = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
@@ -215,7 +210,7 @@ class OptionsApi
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function text()
|
||||
public function text(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
@@ -240,7 +235,7 @@ class OptionsApi
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function value()
|
||||
public function value(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ use Kirby\Cms\Field;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\Collection;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\Obj;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Toolkit\Query;
|
||||
@@ -103,26 +102,7 @@ class OptionsQuery
|
||||
$value = $value[$object];
|
||||
}
|
||||
|
||||
$result = Str::template($value, $data);
|
||||
|
||||
// escape the default queries for the `text` field
|
||||
// TODO: remove after default escape implemented for query templates in 3.6
|
||||
if ($field === 'text') {
|
||||
$defaults = [
|
||||
'arrayItem' => '{{ arrayItem.value }}',
|
||||
'block' => '{{ block.type }}: {{ block.id }}',
|
||||
'file' => '{{ file.filename }}',
|
||||
'page' => '{{ page.title }}',
|
||||
'structureItem' => '{{ structureItem.title }}',
|
||||
'user' => '{{ user.username }}',
|
||||
];
|
||||
|
||||
if (isset($defaults[$object]) && $value === $defaults[$object]) {
|
||||
$result = Escape::html($result);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
return Str::safeTemplate($value, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -19,12 +19,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is boolean
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function boolean(Field $field, $value): bool
|
||||
public static function boolean($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false) {
|
||||
if (is_bool($value) === false) {
|
||||
@@ -40,12 +40,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is valid date
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function date(Field $field, $value): bool
|
||||
public static function date($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false) {
|
||||
if (V::date($value) !== true) {
|
||||
@@ -61,12 +61,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is valid email
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function email(Field $field, $value): bool
|
||||
public static function email($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false) {
|
||||
if (V::email($value) === false) {
|
||||
@@ -82,12 +82,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is maximum
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function max(Field $field, $value): bool
|
||||
public static function max($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false && $field->max() !== null) {
|
||||
if (V::max($value, $field->max()) === false) {
|
||||
@@ -103,12 +103,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is max length
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function maxlength(Field $field, $value): bool
|
||||
public static function maxlength($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false && $field->maxlength() !== null) {
|
||||
if (V::maxLength($value, $field->maxlength()) === false) {
|
||||
@@ -124,12 +124,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is minimum
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function min(Field $field, $value): bool
|
||||
public static function min($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false && $field->min() !== null) {
|
||||
if (V::min($value, $field->min()) === false) {
|
||||
@@ -145,12 +145,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is min length
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function minlength(Field $field, $value): bool
|
||||
public static function minlength($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false && $field->minlength() !== null) {
|
||||
if (V::minLength($value, $field->minlength()) === false) {
|
||||
@@ -166,12 +166,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value matches defined pattern
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function pattern(Field $field, $value): bool
|
||||
public static function pattern($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false && $field->pattern() !== null) {
|
||||
if (V::match($value, '/' . $field->pattern() . '/i') === false) {
|
||||
@@ -187,12 +187,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is required
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function required(Field $field, $value): bool
|
||||
public static function required($field, $value): bool
|
||||
{
|
||||
if ($field->isRequired() === true && $field->save() === true && $field->isEmpty($value) === true) {
|
||||
throw new InvalidArgumentException([
|
||||
@@ -206,12 +206,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is in defined options
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function option(Field $field, $value): bool
|
||||
public static function option($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false) {
|
||||
$values = array_column($field->options(), 'value');
|
||||
@@ -229,12 +229,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field values is in defined options
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function options(Field $field, $value): bool
|
||||
public static function options($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false) {
|
||||
$values = array_column($field->options(), 'value');
|
||||
@@ -253,12 +253,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is valid time
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function time(Field $field, $value): bool
|
||||
public static function time($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false) {
|
||||
if (V::time($value) !== true) {
|
||||
@@ -274,12 +274,12 @@ class Validations
|
||||
/**
|
||||
* Validates if the field value is valid url
|
||||
*
|
||||
* @param \Kirby\Form\Field $field
|
||||
* @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field
|
||||
* @param $value
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function url(Field $field, $value): bool
|
||||
public static function url($field, $value): bool
|
||||
{
|
||||
if ($field->isEmpty($value) === false) {
|
||||
if (V::url($value) === false) {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Kirby\Http;
|
||||
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* The Header class provides methods
|
||||
|
@@ -43,7 +43,7 @@ class Query extends Obj
|
||||
|
||||
public function toString($questionMark = false): string
|
||||
{
|
||||
$query = http_build_query($this);
|
||||
$query = http_build_query($this, null, '&', PHP_QUERY_RFC3986);
|
||||
|
||||
if (empty($query) === true) {
|
||||
return '';
|
||||
|
@@ -5,7 +5,7 @@ namespace Kirby\Http;
|
||||
use Exception;
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -104,6 +104,13 @@ class Remote
|
||||
{
|
||||
$defaults = static::$defaults;
|
||||
|
||||
// use the system CA store by default if
|
||||
// one has been configured in php.ini
|
||||
$cainfo = ini_get('curl.cainfo');
|
||||
if (empty($cainfo) === false && is_file($cainfo) === true) {
|
||||
$defaults['ca'] = self::CA_SYSTEM;
|
||||
}
|
||||
|
||||
// update the defaults with App config if set;
|
||||
// request the App instance lazily
|
||||
$app = App::instance(null, true);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user