Add blueprints and fake content
This commit is contained in:
parent
1ff19bf38f
commit
8235816462
592 changed files with 22385 additions and 31535 deletions
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\Properties;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Asset
|
||||
{
|
||||
use FileFoundation;
|
||||
use FileModifications;
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique id for the asset
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return $this->root();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique media hash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function mediaHash(): string
|
||||
{
|
||||
return crc32($this->filename()) . '-' . $this->modified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative path starting at the media folder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function mediaPath(): string
|
||||
{
|
||||
return 'assets/' . $this->path() . '/' . $this->mediaHash() . '/' . $this->filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the file in the public media folder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function mediaRoot(): string
|
||||
{
|
||||
return $this->kirby()->root('media') . '/' . $this->mediaPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute Url to the file in the public media folder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function mediaUrl(): string
|
||||
{
|
||||
return $this->kirby()->url('media') . '/' . $this->mediaPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the file from the web root,
|
||||
* excluding the filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the path
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
*/
|
||||
protected function setPath(string $path)
|
||||
{
|
||||
$this->path = $path === '.' ? '' : $path;
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -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
Normal file
550
kirby/src/Cms/Core.php
Normal 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(),
|
||||
]);
|
||||
|
||||
|
|
|
@ -1,303 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The Filename class handles complex
|
||||
* mapping of file attributes (i.e for thumbnails)
|
||||
* into human readable filenames.
|
||||
*
|
||||
* ```php
|
||||
* $filename = new Filename('some-file.jpg', '{{ name }}-{{ attributes }}.{{ extension }}', [
|
||||
* 'crop' => 'top left',
|
||||
* 'width' => 300,
|
||||
* 'height' => 200
|
||||
* 'quality' => 80
|
||||
* ]);
|
||||
*
|
||||
* echo $filename->toString();
|
||||
* // result: some-file-300x200-crop-top-left-q80.jpg
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Filename
|
||||
{
|
||||
/**
|
||||
* List of all applicable attributes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes;
|
||||
|
||||
/**
|
||||
* The sanitized file extension
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* The source original filename
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* The sanitized file name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The template for the final name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* Creates a new Filename object
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $template
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(string $filename, string $template, array $attributes = [])
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$this->template = $template;
|
||||
$this->attributes = $attributes;
|
||||
$this->extension = $this->sanitizeExtension(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
$this->name = $this->sanitizeName(pathinfo($filename, PATHINFO_FILENAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the entire object to a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all processed attributes
|
||||
* to an array. The array keys are already
|
||||
* the shortened versions for the filename
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributesToArray(): array
|
||||
{
|
||||
$array = [
|
||||
'dimensions' => implode('x', $this->dimensions()),
|
||||
'crop' => $this->crop(),
|
||||
'blur' => $this->blur(),
|
||||
'bw' => $this->grayscale(),
|
||||
'q' => $this->quality(),
|
||||
];
|
||||
|
||||
$array = array_filter($array, function ($item) {
|
||||
return $item !== null && $item !== false && $item !== '';
|
||||
});
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all processed attributes
|
||||
* to a string, that can be used in the
|
||||
* new filename
|
||||
*
|
||||
* @param string|null $prefix The prefix will be used in the filename creation
|
||||
* @return string
|
||||
*/
|
||||
public function attributesToString(string $prefix = null): string
|
||||
{
|
||||
$array = $this->attributesToArray();
|
||||
$result = [];
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
if ($value === true) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
switch ($key) {
|
||||
case 'dimensions':
|
||||
$result[] = $value;
|
||||
break;
|
||||
case 'crop':
|
||||
$result[] = ($value === 'center') ? null : $key . '-' . $value;
|
||||
break;
|
||||
default:
|
||||
$result[] = $key . $value;
|
||||
}
|
||||
}
|
||||
|
||||
$result = array_filter($result);
|
||||
$attributes = implode('-', $result);
|
||||
|
||||
if (empty($attributes) === true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $prefix . $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the blur option value
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
public function blur()
|
||||
{
|
||||
$value = $this->attributes['blur'] ?? false;
|
||||
|
||||
if ($value === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the crop option value
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function crop()
|
||||
{
|
||||
// get the crop value
|
||||
$crop = $this->attributes['crop'] ?? false;
|
||||
|
||||
if ($crop === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Str::slug($crop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized array
|
||||
* with width and height values
|
||||
* if available
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dimensions()
|
||||
{
|
||||
if (empty($this->attributes['width']) === true && empty($this->attributes['height']) === true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'width' => $this->attributes['width'] ?? null,
|
||||
'height' => $this->attributes['height'] ?? null
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sanitized extension
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function extension(): string
|
||||
{
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the grayscale option value
|
||||
* and also the available ways to write
|
||||
* the option. You can use `grayscale`,
|
||||
* `greyscale` or simply `bw`. The function
|
||||
* will always return `grayscale`
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function grayscale(): bool
|
||||
{
|
||||
// normalize options
|
||||
$value = $this->attributes['grayscale'] ?? $this->attributes['greyscale'] ?? $this->attributes['bw'] ?? false;
|
||||
|
||||
// turn anything into boolean
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename without extension
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the quality option value
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
public function quality()
|
||||
{
|
||||
$value = $this->attributes['quality'] ?? false;
|
||||
|
||||
if ($value === false || $value === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the file extension.
|
||||
* The extension will be converted
|
||||
* to lowercase and `jpeg` will be
|
||||
* replaced with `jpg`
|
||||
*
|
||||
* @param string $extension
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitizeExtension(string $extension): string
|
||||
{
|
||||
$extension = strtolower($extension);
|
||||
$extension = str_replace('jpeg', 'jpg', $extension);
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the name with Kirby's
|
||||
* Str::slug function
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitizeName(string $name): string
|
||||
{
|
||||
return Str::slug($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the converted filename as string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return Str::template($this->template, [
|
||||
'name' => $this->name(),
|
||||
'attributes' => $this->attributesToString('-'),
|
||||
'extension' => $this->extension()
|
||||
], '');
|
||||
}
|
||||
}
|
|
@ -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
Normal file
191
kirby/src/Cms/Find.php
Normal 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
Normal file
250
kirby/src/Cms/Loader.php
Normal 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());
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The PanelPlugins class takes care of collecting
|
||||
* js and css plugin files for the panel and caches
|
||||
* them in the media folder
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class PanelPlugins
|
||||
{
|
||||
/**
|
||||
* Cache of all collected plugin files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $files;
|
||||
|
||||
/**
|
||||
* Collects and returns the plugin files for all plugins
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function files(): array
|
||||
{
|
||||
if ($this->files !== null) {
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
$this->files = [];
|
||||
|
||||
foreach (App::instance()->plugins() as $plugin) {
|
||||
$this->files[] = $plugin->root() . '/index.css';
|
||||
$this->files[] = $plugin->root() . '/index.js';
|
||||
}
|
||||
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification
|
||||
* of the collected plugin files
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function modified(): int
|
||||
{
|
||||
$files = $this->files();
|
||||
$modified = [0];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$modified[] = F::modified($file);
|
||||
}
|
||||
|
||||
return max($modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the files from all plugins and concatenate them
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function read(string $type): string
|
||||
{
|
||||
$dist = [];
|
||||
|
||||
foreach ($this->files() as $file) {
|
||||
if (F::extension($file) === $type) {
|
||||
if ($content = F::read($file)) {
|
||||
if ($type === 'js') {
|
||||
$content = trim($content);
|
||||
|
||||
// make sure that each plugin is ended correctly
|
||||
if (Str::endsWith($content, ';') === false) {
|
||||
$content .= ';';
|
||||
}
|
||||
}
|
||||
|
||||
$dist[] = $content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode(PHP_EOL . PHP_EOL, $dist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute url to the cache file
|
||||
* This is used by the panel to link the plugins
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function url(string $type): string
|
||||
{
|
||||
return App::instance()->url('media') . '/plugins/index.' . $type . '?' . $this->modified();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue