3.4.0
This commit is contained in:
@@ -39,7 +39,8 @@ class Api extends BaseApi
|
||||
|
||||
$this->kirby->setCurrentLanguage($this->language());
|
||||
|
||||
if ($user = $this->kirby->user()) {
|
||||
$allowImpersonation = $this->kirby()->option('api.allowImpersonation', false);
|
||||
if ($user = $this->kirby->user(null, $allowImpersonation)) {
|
||||
$this->kirby->setCurrentTranslation($user->language());
|
||||
}
|
||||
|
||||
@@ -95,8 +96,9 @@ class Api extends BaseApi
|
||||
public function file(string $path = null, string $filename)
|
||||
{
|
||||
$filename = urldecode($filename);
|
||||
$file = $this->parent($path)->file($filename);
|
||||
|
||||
if ($file = $this->parent($path)->file($filename)) {
|
||||
if ($file && $file->isReadable() === true) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
@@ -136,7 +138,7 @@ class Api extends BaseApi
|
||||
$model = $kirby->site();
|
||||
break;
|
||||
case 'account':
|
||||
$model = $kirby->user();
|
||||
$model = $kirby->user(null, $kirby->option('api.allowImpersonation', false));
|
||||
break;
|
||||
case 'page':
|
||||
$id = str_replace(['+', ' '], '/', basename($path));
|
||||
@@ -192,7 +194,7 @@ class Api extends BaseApi
|
||||
$id = str_replace('+', '/', $id);
|
||||
$page = $this->kirby->page($id);
|
||||
|
||||
if ($page && $page->isReadable()) {
|
||||
if ($page && $page->isReadable() === true) {
|
||||
return $page;
|
||||
}
|
||||
|
||||
@@ -242,7 +244,7 @@ class Api extends BaseApi
|
||||
{
|
||||
// get the authenticated user
|
||||
if ($id === null) {
|
||||
return $this->kirby->auth()->user();
|
||||
return $this->kirby->auth()->user(null, $this->kirby()->option('api.allowImpersonation', false));
|
||||
}
|
||||
|
||||
// get a specific user by id
|
||||
|
||||
@@ -6,6 +6,7 @@ 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\Http\Request;
|
||||
use Kirby\Http\Router;
|
||||
@@ -18,6 +19,7 @@ use Kirby\Toolkit\Controller;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* The `$kirby` object is the app instance of
|
||||
@@ -44,7 +46,6 @@ class App
|
||||
use Properties;
|
||||
|
||||
protected static $instance;
|
||||
protected static $root;
|
||||
protected static $version;
|
||||
|
||||
public $data = [];
|
||||
@@ -74,6 +75,14 @@ class App
|
||||
protected $users;
|
||||
protected $visitor;
|
||||
|
||||
/**
|
||||
* List of options that shouldn't be converted
|
||||
* to a tree structure by dot syntax
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $nestIgnoreOptions = ['hooks'];
|
||||
|
||||
/**
|
||||
* Creates a new App instance
|
||||
*
|
||||
@@ -81,9 +90,6 @@ class App
|
||||
*/
|
||||
public function __construct(array $props = [])
|
||||
{
|
||||
// the kirby folder directory
|
||||
static::$root = dirname(__DIR__, 2);
|
||||
|
||||
// register all roots to be able to load stuff afterwards
|
||||
$this->bakeRoots($props['roots'] ?? []);
|
||||
|
||||
@@ -91,6 +97,9 @@ class App
|
||||
$this->optionsFromConfig();
|
||||
$this->optionsFromProps($props['options'] ?? []);
|
||||
|
||||
// register the Whoops error handler
|
||||
$this->handleErrors();
|
||||
|
||||
// set the path to make it available for the url bakery
|
||||
$this->setPath($props['path'] ?? null);
|
||||
|
||||
@@ -118,20 +127,22 @@ class App
|
||||
$this->extensionsFromSystem();
|
||||
$this->extensionsFromProps($props);
|
||||
$this->extensionsFromPlugins();
|
||||
$this->extensionsFromOptions();
|
||||
$this->extensionsFromFolders();
|
||||
|
||||
// bake the options for the first time
|
||||
$this->bakeOptions();
|
||||
|
||||
// register the extensions from the normalized options
|
||||
$this->extensionsFromOptions();
|
||||
|
||||
// trigger hook for use in plugins
|
||||
$this->trigger('system.loadPlugins:after');
|
||||
|
||||
// handle those damn errors
|
||||
$this->handleErrors();
|
||||
|
||||
// execute a ready callback from the config
|
||||
$this->optionsFromReadyCallback();
|
||||
|
||||
// bake config
|
||||
Config::$data = $this->options;
|
||||
// bake the options again with those from the ready callback
|
||||
$this->bakeOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +175,7 @@ class App
|
||||
return $this->api;
|
||||
}
|
||||
|
||||
$root = static::$root . '/config/api';
|
||||
$root = $this->root('kirby') . '/config/api';
|
||||
$extensions = $this->extensions['api'] ?? [];
|
||||
$routes = (include $root . '/routes.php')($this);
|
||||
|
||||
@@ -182,37 +193,54 @@ class App
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a hook to the given value;
|
||||
* the value that gets modified by the hooks
|
||||
* is always the last argument
|
||||
* Applies a hook to the given value
|
||||
*
|
||||
* @internal
|
||||
* @param string $name Hook name
|
||||
* @param mixed ...$args Arguments to pass to the hooks
|
||||
* @param string $name Full event name
|
||||
* @param array $args Associative array of named event arguments
|
||||
* @param string $modify Key in $args that is modified by the hooks
|
||||
* @param \Kirby\Cms\Event|null $originalEvent Event object (internal use)
|
||||
* @return mixed Resulting value as modified by the hooks
|
||||
*/
|
||||
public function apply(string $name, ...$args)
|
||||
public function apply(string $name, array $args, string $modify, ?Event $originalEvent = null)
|
||||
{
|
||||
// split up args into "passive" args and the value
|
||||
$value = array_pop($args);
|
||||
$event = $originalEvent ?? new Event($name, $args);
|
||||
|
||||
if ($functions = $this->extension('hooks', $name)) {
|
||||
foreach ($functions as $function) {
|
||||
// re-assemble args
|
||||
$hookArgs = $args;
|
||||
$hookArgs[] = $value;
|
||||
|
||||
// bind the App object to the hook
|
||||
$newValue = $function->call($this, ...$hookArgs);
|
||||
$newValue = $event->call($this, $function);
|
||||
|
||||
// update value if one was returned
|
||||
if ($newValue !== null) {
|
||||
$value = $newValue;
|
||||
$event->updateArgument($modify, $newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
// apply wildcard hooks if available
|
||||
$nameWildcards = $event->nameWildcards();
|
||||
if ($originalEvent === null && count($nameWildcards) > 0) {
|
||||
foreach ($nameWildcards as $nameWildcard) {
|
||||
// the $event object is passed by reference
|
||||
// and will be modified down the chain
|
||||
$this->apply($nameWildcard, $event->arguments(), $modify, $event);
|
||||
}
|
||||
}
|
||||
|
||||
return $event->argument($modify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes and globally sets the configured options
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
protected function bakeOptions()
|
||||
{
|
||||
$this->options = A::nest($this->options, static::$nestIgnoreOptions);
|
||||
Config::$data = $this->options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +251,7 @@ class App
|
||||
*/
|
||||
protected function bakeRoots(array $roots = null)
|
||||
{
|
||||
$roots = array_merge(require static::$root . '/config/roots.php', (array)$roots);
|
||||
$roots = array_merge(require dirname(__DIR__, 2) . '/config/roots.php', (array)$roots);
|
||||
$this->roots = Ingredients::bake($roots);
|
||||
return $this;
|
||||
}
|
||||
@@ -241,7 +269,7 @@ class App
|
||||
$urls['index'] = $this->options['url'];
|
||||
}
|
||||
|
||||
$urls = array_merge(require static::$root . '/config/urls.php', (array)$urls);
|
||||
$urls = array_merge(require $this->root('kirby') . '/config/urls.php', (array)$urls);
|
||||
$this->urls = Ingredients::bake($urls);
|
||||
return $this;
|
||||
}
|
||||
@@ -285,11 +313,11 @@ class App
|
||||
$router = $this->router();
|
||||
|
||||
$router::$beforeEach = function ($route, $path, $method) {
|
||||
$this->trigger('route:before', $route, $path, $method);
|
||||
$this->trigger('route:before', compact('route', 'path', 'method'));
|
||||
};
|
||||
|
||||
$router::$afterEach = function ($route, $path, $method, $result) {
|
||||
return $this->apply('route:after', $route, $path, $method, $result);
|
||||
$router::$afterEach = function ($route, $path, $method, $result, $final) {
|
||||
return $this->apply('route:after', compact('route', 'path', 'method', 'result', 'final'), 'result');
|
||||
};
|
||||
|
||||
return $router->call($path ?? $this->path(), $method ?? $this->request()->method());
|
||||
@@ -357,6 +385,31 @@ class App
|
||||
return $this->options['content']['ignore'] ?? Dir::$ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a non-guessable token based on model
|
||||
* data and a configured salt
|
||||
*
|
||||
* @param mixed $model Object to pass to the salt callback if configured
|
||||
* @param string $value Model data to include in the generated token
|
||||
* @return string
|
||||
*/
|
||||
public function contentToken($model, string $value): string
|
||||
{
|
||||
if (method_exists($model, 'root') === true) {
|
||||
$default = $model->root();
|
||||
} else {
|
||||
$default = $this->root('content');
|
||||
}
|
||||
|
||||
$salt = $this->option('content.salt', $default);
|
||||
|
||||
if (is_a($salt, 'Closure') === true) {
|
||||
$salt = $salt($model);
|
||||
}
|
||||
|
||||
return hash_hmac('sha1', $value, $salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a page controller by name
|
||||
* and with the given arguments
|
||||
@@ -524,12 +577,17 @@ class App
|
||||
* Returns the current App instance
|
||||
*
|
||||
* @param \Kirby\Cms\App $instance
|
||||
* @return self
|
||||
* @param bool $lazy If `true`, the instance is only returned if already existing
|
||||
* @return self|null
|
||||
*/
|
||||
public static function instance(self $instance = null)
|
||||
public static function instance(self $instance = null, bool $lazy = false)
|
||||
{
|
||||
if ($instance === null) {
|
||||
return static::$instance ?? new static();
|
||||
if ($lazy === true) {
|
||||
return static::$instance;
|
||||
} else {
|
||||
return static::$instance ?? new static();
|
||||
}
|
||||
}
|
||||
|
||||
return static::$instance = $instance;
|
||||
@@ -659,7 +717,7 @@ class App
|
||||
$data['site'] = $data['site'] ?? $data['kirby']->site();
|
||||
$data['parent'] = $data['parent'] ?? $data['site']->page();
|
||||
|
||||
return KirbyTags::parse($text, $data, $this->options, $this->extensions['hooks']);
|
||||
return KirbyTags::parse($text, $data, $this->options, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -673,7 +731,7 @@ class App
|
||||
*/
|
||||
public function kirbytext(string $text = null, array $data = [], bool $inline = false): string
|
||||
{
|
||||
$text = $this->apply('kirbytext:before', $text);
|
||||
$text = $this->apply('kirbytext:before', compact('text'), 'text');
|
||||
$text = $this->kirbytags($text, $data);
|
||||
$text = $this->markdown($text, $inline);
|
||||
|
||||
@@ -681,7 +739,7 @@ class App
|
||||
$text = $this->smartypants($text);
|
||||
}
|
||||
|
||||
$text = $this->apply('kirbytext:after', $text);
|
||||
$text = $this->apply('kirbytext:after', compact('text'), 'text');
|
||||
|
||||
return $text;
|
||||
}
|
||||
@@ -859,6 +917,27 @@ class App
|
||||
|
||||
// inject all last-minute options recursively
|
||||
$this->options = array_replace_recursive($this->options, $options);
|
||||
|
||||
// update the system with changed options
|
||||
if (
|
||||
isset($options['debug']) === true ||
|
||||
isset($options['whoops']) === true ||
|
||||
isset($options['editor']) === true
|
||||
) {
|
||||
$this->handleErrors();
|
||||
}
|
||||
|
||||
if (isset($options['debug']) === true) {
|
||||
$this->api = null;
|
||||
}
|
||||
|
||||
if (isset($options['home']) === true || isset($options['error']) === true) {
|
||||
$this->site = null;
|
||||
}
|
||||
|
||||
if (isset($options['slugs']) === true) {
|
||||
$this->i18n();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->options;
|
||||
@@ -1091,7 +1170,7 @@ class App
|
||||
}
|
||||
|
||||
$registry = $this->extensions('routes');
|
||||
$system = (include static::$root . '/config/routes.php')($this);
|
||||
$system = (include $this->root('kirby') . '/config/routes.php')($this);
|
||||
$routes = array_merge($system['before'], $registry, $system['after']);
|
||||
|
||||
return $this->routes = $routes;
|
||||
@@ -1312,12 +1391,15 @@ class App
|
||||
* Trigger a hook by name
|
||||
*
|
||||
* @internal
|
||||
* @param string $name
|
||||
* @param mixed ...$arguments
|
||||
* @param string $name Full event name
|
||||
* @param array $args Associative array of named event arguments
|
||||
* @param \Kirby\Cms\Event|null $originalEvent Event object (internal use)
|
||||
* @return void
|
||||
*/
|
||||
public function trigger(string $name, ...$arguments)
|
||||
public function trigger(string $name, array $args = [], ?Event $originalEvent = null)
|
||||
{
|
||||
$event = $originalEvent ?? new Event($name, $args);
|
||||
|
||||
if ($functions = $this->extension('hooks', $name)) {
|
||||
static $level = 0;
|
||||
static $triggered = [];
|
||||
@@ -1332,7 +1414,7 @@ class App
|
||||
$triggered[$name][] = $function;
|
||||
|
||||
// bind the App object to the hook
|
||||
$function->call($this, ...$arguments);
|
||||
$event->call($this, $function);
|
||||
}
|
||||
|
||||
$level--;
|
||||
@@ -1341,6 +1423,14 @@ class App
|
||||
$triggered = [];
|
||||
}
|
||||
}
|
||||
|
||||
// trigger wildcard hooks if available
|
||||
$nameWildcards = $event->nameWildcards();
|
||||
if ($originalEvent === null && count($nameWildcards) > 0) {
|
||||
foreach ($nameWildcards as $nameWildcard) {
|
||||
$this->trigger($nameWildcard, $args, $event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1372,7 +1462,11 @@ class App
|
||||
*/
|
||||
public static function version(): ?string
|
||||
{
|
||||
return static::$version = static::$version ?? Data::read(static::$root . '/composer.json')['version'] ?? null;
|
||||
try {
|
||||
return static::$version = static::$version ?? Data::read(dirname(__DIR__, 2) . '/composer.json')['version'] ?? null;
|
||||
} catch (Throwable $e) {
|
||||
throw new LogicException('The Kirby version cannot be detected. The composer.json is probably missing or not readable.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Run as Whoops;
|
||||
|
||||
/**
|
||||
* AppErrors
|
||||
* PHP error handling using the Whoops library
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
@@ -20,14 +20,30 @@ use Whoops\Run as Whoops;
|
||||
*/
|
||||
trait AppErrors
|
||||
{
|
||||
/**
|
||||
* Whoops instance cache
|
||||
*
|
||||
* @var \Whoops\Run
|
||||
*/
|
||||
protected $whoops;
|
||||
|
||||
/**
|
||||
* Registers the PHP error handler for CLI usage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleCliErrors(): void
|
||||
{
|
||||
$whoops = new Whoops();
|
||||
$whoops->pushHandler(new PlainTextHandler());
|
||||
$whoops->register();
|
||||
$this->setWhoopsHandler(new PlainTextHandler());
|
||||
}
|
||||
|
||||
protected function handleErrors()
|
||||
/**
|
||||
* Registers the PHP error handler
|
||||
* based on the environment
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleErrors(): void
|
||||
{
|
||||
if ($this->request()->cli() === true) {
|
||||
$this->handleCliErrors();
|
||||
@@ -42,21 +58,25 @@ trait AppErrors
|
||||
$this->handleHtmlErrors();
|
||||
}
|
||||
|
||||
protected function handleHtmlErrors()
|
||||
/**
|
||||
* Registers the PHP error handler for HTML output
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleHtmlErrors(): void
|
||||
{
|
||||
$whoops = new Whoops();
|
||||
$handler = null;
|
||||
|
||||
if ($this->option('debug') === true) {
|
||||
if ($this->option('whoops', true) === true) {
|
||||
$handler = new PrettyPageHandler();
|
||||
$handler->setPageTitle('Kirby CMS Debugger');
|
||||
$handler->setResourcesPath(dirname(__DIR__, 2) . '/assets');
|
||||
$handler->addCustomCss('whoops.css');
|
||||
|
||||
if ($editor = $this->option('editor')) {
|
||||
$handler->setEditor($editor);
|
||||
}
|
||||
|
||||
$whoops->pushHandler($handler);
|
||||
$whoops->register();
|
||||
}
|
||||
} else {
|
||||
$handler = new CallbackHandler(function ($exception, $inspector, $run) {
|
||||
@@ -65,20 +85,27 @@ trait AppErrors
|
||||
if (is_a($fatal, 'Closure') === true) {
|
||||
echo $fatal($this);
|
||||
} else {
|
||||
include static::$root . '/views/fatal.php';
|
||||
include $this->root('kirby') . '/views/fatal.php';
|
||||
}
|
||||
|
||||
return Handler::QUIT;
|
||||
});
|
||||
}
|
||||
|
||||
$whoops->pushHandler($handler);
|
||||
$whoops->register();
|
||||
if ($handler !== null) {
|
||||
$this->setWhoopsHandler($handler);
|
||||
} else {
|
||||
$this->unsetWhoopsHandler();
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleJsonErrors()
|
||||
/**
|
||||
* Registers the PHP error handler for JSON output
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleJsonErrors(): void
|
||||
{
|
||||
$whoops = new Whoops();
|
||||
$handler = new CallbackHandler(function ($exception, $inspector, $run) {
|
||||
if (is_a($exception, 'Kirby\Exception\Exception') === true) {
|
||||
$httpCode = $exception->getHttpCode();
|
||||
@@ -112,7 +139,46 @@ trait AppErrors
|
||||
return Handler::QUIT;
|
||||
});
|
||||
|
||||
$this->setWhoopsHandler($handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables Whoops with the specified handler
|
||||
*
|
||||
* @param Callable|\Whoops\Handler\HandlerInterface $handler
|
||||
* @return void
|
||||
*/
|
||||
protected function setWhoopsHandler($handler): void
|
||||
{
|
||||
$whoops = $this->whoops();
|
||||
$whoops->clearHandlers();
|
||||
$whoops->pushHandler($handler);
|
||||
$whoops->register();
|
||||
$whoops->register(); // will only do something if not already registered
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the Whoops handlers and disables Whoops
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function unsetWhoopsHandler(): void
|
||||
{
|
||||
$whoops = $this->whoops();
|
||||
$whoops->clearHandlers();
|
||||
$whoops->unregister(); // will only do something if currently registered
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Whoops error handler instance
|
||||
*
|
||||
* @return \Whoops\Run
|
||||
*/
|
||||
protected function whoops()
|
||||
{
|
||||
if ($this->whoops !== null) {
|
||||
return $this->whoops;
|
||||
}
|
||||
|
||||
return $this->whoops = new Whoops();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Form\Field as FormField;
|
||||
use Kirby\Text\KirbyTag;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Collection;
|
||||
use Kirby\Toolkit\Collection as ToolkitCollection;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\V;
|
||||
@@ -30,6 +30,13 @@ trait AppPlugins
|
||||
*/
|
||||
protected static $plugins = [];
|
||||
|
||||
/**
|
||||
* Cache for system extensions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $systemExtensions = null;
|
||||
|
||||
/**
|
||||
* The extension registry
|
||||
*
|
||||
@@ -47,6 +54,7 @@ trait AppPlugins
|
||||
'components' => [],
|
||||
'controllers' => [],
|
||||
'collectionFilters' => [],
|
||||
'collectionMethods' => [],
|
||||
'fieldMethods' => [],
|
||||
'fileMethods' => [],
|
||||
'filesMethods' => [],
|
||||
@@ -56,12 +64,14 @@ trait AppPlugins
|
||||
'pageMethods' => [],
|
||||
'pagesMethods' => [],
|
||||
'pageModels' => [],
|
||||
'permissions' => [],
|
||||
'routes' => [],
|
||||
'sections' => [],
|
||||
'siteMethods' => [],
|
||||
'snippets' => [],
|
||||
'tags' => [],
|
||||
'templates' => [],
|
||||
'thirdParty' => [],
|
||||
'translations' => [],
|
||||
'userMethods' => [],
|
||||
'userModels' => [],
|
||||
@@ -69,13 +79,6 @@ trait AppPlugins
|
||||
'validators' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Cache for system extensions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $systemExtensions = null;
|
||||
|
||||
/**
|
||||
* Flag when plugins have been loaded
|
||||
* to not load them again
|
||||
@@ -152,7 +155,18 @@ trait AppPlugins
|
||||
*/
|
||||
protected function extendCollectionFilters(array $filters): array
|
||||
{
|
||||
return $this->extensions['collectionFilters'] = Collection::$filters = array_merge(Collection::$filters, $filters);
|
||||
return $this->extensions['collectionFilters'] = ToolkitCollection::$filters = array_merge(ToolkitCollection::$filters, $filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional collection methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendCollectionMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['collectionMethods'] = Collection::$methods = array_merge(Collection::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,6 +301,10 @@ trait AppPlugins
|
||||
$options = $prefixed;
|
||||
}
|
||||
|
||||
// register each option in the nesting blacklist;
|
||||
// this prevents Kirby from nesting the array keys inside each option
|
||||
static::$nestIgnoreOptions = array_merge(static::$nestIgnoreOptions, array_keys($options));
|
||||
|
||||
return $this->extensions['options'] = $this->options = A::merge($options, $this->options, A::MERGE_REPLACE);
|
||||
}
|
||||
|
||||
@@ -334,6 +352,22 @@ trait AppPlugins
|
||||
return $this->extensions['pages'] = array_merge($this->extensions['pages'], $pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional permissions
|
||||
*
|
||||
* @param array $permissions
|
||||
* @param \Kirby\Cms\Plugin|null $plugin
|
||||
* @return array
|
||||
*/
|
||||
protected function extendPermissions(array $permissions, Plugin $plugin = null): array
|
||||
{
|
||||
if ($plugin !== null) {
|
||||
$permissions = [$plugin->prefix() => $permissions];
|
||||
}
|
||||
|
||||
return $this->extensions['permissions'] = Permissions::$extendedActions = array_merge(Permissions::$extendedActions, $permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional routes
|
||||
*
|
||||
@@ -426,6 +460,20 @@ trait AppPlugins
|
||||
return $this->extensions['translations'] = array_replace_recursive($this->extensions['translations'], $translations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add third party extensions to the registry
|
||||
* so they can be used as plugins for plugins
|
||||
* for example.
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $extensions
|
||||
* @return array
|
||||
*/
|
||||
protected function extendThirdParty(array $extensions): array
|
||||
{
|
||||
return $this->extensions['thirdParty'] = array_replace_recursive($this->extensions['thirdParty'], $extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional user methods
|
||||
*
|
||||
@@ -514,7 +562,7 @@ trait AppPlugins
|
||||
$class = str_replace(['.', '-', '_'], '', $name) . 'Page';
|
||||
|
||||
// load the model class
|
||||
include_once $model;
|
||||
F::loadOnce($model);
|
||||
|
||||
if (class_exists($class) === true) {
|
||||
$models[$name] = $class;
|
||||
@@ -577,16 +625,18 @@ trait AppPlugins
|
||||
*/
|
||||
protected function extensionsFromSystem()
|
||||
{
|
||||
$root = $this->root('kirby');
|
||||
|
||||
// load static extensions only once
|
||||
if (static::$systemExtensions === null) {
|
||||
// Form Field Mixins
|
||||
FormField::$mixins['filepicker'] = include static::$root . '/config/fields/mixins/filepicker.php';
|
||||
FormField::$mixins['min'] = include static::$root . '/config/fields/mixins/min.php';
|
||||
FormField::$mixins['options'] = include static::$root . '/config/fields/mixins/options.php';
|
||||
FormField::$mixins['pagepicker'] = include static::$root . '/config/fields/mixins/pagepicker.php';
|
||||
FormField::$mixins['picker'] = include static::$root . '/config/fields/mixins/picker.php';
|
||||
FormField::$mixins['upload'] = include static::$root . '/config/fields/mixins/upload.php';
|
||||
FormField::$mixins['userpicker'] = include static::$root . '/config/fields/mixins/userpicker.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';
|
||||
|
||||
// Tag Aliases
|
||||
KirbyTag::$aliases = [
|
||||
@@ -612,32 +662,32 @@ trait AppPlugins
|
||||
];
|
||||
|
||||
// blueprint presets
|
||||
PageBlueprint::$presets['pages'] = include static::$root . '/config/presets/pages.php';
|
||||
PageBlueprint::$presets['page'] = include static::$root . '/config/presets/page.php';
|
||||
PageBlueprint::$presets['files'] = include static::$root . '/config/presets/files.php';
|
||||
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 static::$root . '/config/sections/mixins/empty.php';
|
||||
Section::$mixins['headline'] = include static::$root . '/config/sections/mixins/headline.php';
|
||||
Section::$mixins['help'] = include static::$root . '/config/sections/mixins/help.php';
|
||||
Section::$mixins['layout'] = include static::$root . '/config/sections/mixins/layout.php';
|
||||
Section::$mixins['max'] = include static::$root . '/config/sections/mixins/max.php';
|
||||
Section::$mixins['min'] = include static::$root . '/config/sections/mixins/min.php';
|
||||
Section::$mixins['pagination'] = include static::$root . '/config/sections/mixins/pagination.php';
|
||||
Section::$mixins['parent'] = include static::$root . '/config/sections/mixins/parent.php';
|
||||
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 static::$root . '/config/sections/info.php';
|
||||
Section::$types['pages'] = include static::$root . '/config/sections/pages.php';
|
||||
Section::$types['files'] = include static::$root . '/config/sections/files.php';
|
||||
Section::$types['fields'] = include static::$root . '/config/sections/fields.php';
|
||||
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 static::$root . '/config/components.php',
|
||||
'blueprints' => include static::$root . '/config/blueprints.php',
|
||||
'fields' => include static::$root . '/config/fields.php',
|
||||
'fieldMethods' => include static::$root . '/config/methods.php',
|
||||
'tags' => include static::$root . '/config/tags.php'
|
||||
'components' => include $root . '/config/components.php',
|
||||
'blueprints' => include $root . '/config/blueprints.php',
|
||||
'fields' => include $root . '/config/fields.php',
|
||||
'fieldMethods' => include $root . '/config/methods.php',
|
||||
'tags' => include $root . '/config/tags.php'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -656,6 +706,18 @@ trait AppPlugins
|
||||
$this->extendTags(static::$systemExtensions['tags']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the native implementation
|
||||
* of a core component
|
||||
*
|
||||
* @param string $component
|
||||
* @return \Closure | false
|
||||
*/
|
||||
public function nativeComponent(string $component)
|
||||
{
|
||||
return static::$systemExtensions['components'][$component] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kirby plugin factory and getter
|
||||
*
|
||||
@@ -733,7 +795,7 @@ trait AppPlugins
|
||||
continue;
|
||||
}
|
||||
|
||||
include_once $entry;
|
||||
F::loadOnce($entry);
|
||||
|
||||
$loaded[] = $dir;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -36,12 +37,34 @@ trait AppUsers
|
||||
/**
|
||||
* Become any existing user
|
||||
*
|
||||
* @param string|null $who
|
||||
* @return \Kirby\Cms\User|null
|
||||
* @param string|null $who User ID or email address
|
||||
* @param Closure|null $callback Optional action function that will be run with
|
||||
* the permissions of the impersonated user; the
|
||||
* impersonation will be reset afterwards
|
||||
* @return mixed If called without callback: User that was impersonated;
|
||||
* if called with callback: Return value from the callback
|
||||
*/
|
||||
public function impersonate(string $who = null)
|
||||
public function impersonate(?string $who = null, ?Closure $callback = null)
|
||||
{
|
||||
return $this->auth()->impersonate($who);
|
||||
$auth = $this->auth();
|
||||
|
||||
$userBefore = $auth->currentUserFromImpersonation();
|
||||
$userAfter = $auth->impersonate($who);
|
||||
|
||||
if ($callback === null) {
|
||||
return $userAfter;
|
||||
}
|
||||
|
||||
try {
|
||||
// bind the App object to the callback
|
||||
return $callback->call($this, $userAfter);
|
||||
} catch (Throwable $e) {
|
||||
throw $e;
|
||||
} finally {
|
||||
// ensure that the impersonation is *always* reset
|
||||
// to the original value, even if an error occurred
|
||||
$auth->impersonate($userBefore !== null ? $userBefore->id() : null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,20 +100,23 @@ trait AppUsers
|
||||
* Returns a specific user by id
|
||||
* or the current user if no id is given
|
||||
*
|
||||
* @param string $id
|
||||
* @param string|null $id
|
||||
* @param bool $allowImpersonation If set to false, only the actually
|
||||
* logged in user will be returned
|
||||
* (when `$id` is passed as `null`)
|
||||
* @return \Kirby\Cms\User|null
|
||||
*/
|
||||
public function user(string $id = null)
|
||||
public function user(?string $id = null, bool $allowImpersonation = true)
|
||||
{
|
||||
if ($id !== null) {
|
||||
return $this->users()->find($id);
|
||||
}
|
||||
|
||||
if (is_string($this->user) === true) {
|
||||
if ($allowImpersonation === true && is_string($this->user) === true) {
|
||||
return $this->auth()->impersonate($this->user);
|
||||
} else {
|
||||
try {
|
||||
return $this->auth()->user();
|
||||
return $this->auth()->user(null, $allowImpersonation);
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,16 @@ class Auth
|
||||
return $this->validatePassword($auth->username(), $auth->password());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently impersonated user
|
||||
*
|
||||
* @return \Kirby\Cms\User|null
|
||||
*/
|
||||
public function currentUserFromImpersonation()
|
||||
{
|
||||
return $this->impersonate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logged in user by checking
|
||||
* the current session and finding a valid
|
||||
@@ -124,10 +134,10 @@ class Auth
|
||||
/**
|
||||
* Become any existing user
|
||||
*
|
||||
* @param string|null $who
|
||||
* @param string|null $who User ID or email address
|
||||
* @return \Kirby\Cms\User|null
|
||||
*/
|
||||
public function impersonate(string $who = null)
|
||||
public function impersonate(?string $who = null)
|
||||
{
|
||||
switch ($who) {
|
||||
case null:
|
||||
@@ -416,16 +426,19 @@ class Auth
|
||||
/**
|
||||
* Returns the current authentication type
|
||||
*
|
||||
* @param bool $allowImpersonation If set to false, 'impersonate' won't
|
||||
* be returned as authentication type
|
||||
* even if an impersonation is active
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
public function type(bool $allowImpersonation = true): string
|
||||
{
|
||||
$basicAuth = $this->kirby->option('api.basicAuth', false);
|
||||
$auth = $this->kirby->request()->auth();
|
||||
|
||||
if ($basicAuth === true && $auth && $auth->type() === 'basic') {
|
||||
return 'basic';
|
||||
} elseif ($this->impersonate !== null) {
|
||||
} elseif ($allowImpersonation === true && $this->impersonate !== null) {
|
||||
return 'impersonate';
|
||||
} else {
|
||||
return 'session';
|
||||
@@ -436,13 +449,15 @@ class Auth
|
||||
* Validates the currently logged in user
|
||||
*
|
||||
* @param \Kirby\Session\Session|array|null $session
|
||||
* @param bool $allowImpersonation If set to false, only the actually
|
||||
* logged in user will be returned
|
||||
* @return \Kirby\Cms\User
|
||||
*
|
||||
* @throws \Throwable If an authentication error occured
|
||||
*/
|
||||
public function user($session = null)
|
||||
public function user($session = null, bool $allowImpersonation = true)
|
||||
{
|
||||
if ($this->impersonate !== null) {
|
||||
if ($allowImpersonation === true && $this->impersonate !== null) {
|
||||
return $this->impersonate;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class Collection extends BaseCollection
|
||||
public function __call(string $key, $arguments)
|
||||
{
|
||||
// collection methods
|
||||
if ($this->hasMethod($key)) {
|
||||
if ($this->hasMethod($key) === true) {
|
||||
return $this->callMethod($key, $arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\Controller;
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
/**
|
||||
* Manages and loads all collections
|
||||
@@ -119,8 +120,8 @@ class Collections
|
||||
// first check for collection file
|
||||
$file = $kirby->root('collections') . '/' . $name . '.php';
|
||||
|
||||
if (file_exists($file)) {
|
||||
$collection = require $file;
|
||||
if (is_file($file) === true) {
|
||||
$collection = F::load($file);
|
||||
|
||||
if (is_a($collection, 'Closure')) {
|
||||
return $collection;
|
||||
|
||||
@@ -257,6 +257,10 @@ class Content
|
||||
public function update(array $content = null, bool $overwrite = false)
|
||||
{
|
||||
$this->data = $overwrite === true ? (array)$content : array_merge($this->data, (array)$content);
|
||||
|
||||
// clear cache of Field objects
|
||||
$this->fields = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Yaml;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
@@ -108,7 +108,7 @@ class ContentLocks
|
||||
// always read the whole file
|
||||
rewind($handle);
|
||||
$string = fread($handle, $filesize);
|
||||
$data = Yaml::decode($string);
|
||||
$data = Data::decode($string, 'yaml');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ class ContentLocks
|
||||
return F::remove($file);
|
||||
}
|
||||
|
||||
$yaml = Yaml::encode($this->data[$file]);
|
||||
$yaml = Data::encode($this->data[$file], 'yaml');
|
||||
|
||||
// delete all file contents first
|
||||
if (rewind($handle) !== true || ftruncate($handle, 0) !== true) {
|
||||
|
||||
288
kirby/src/Cms/Event.php
Executable file
288
kirby/src/Cms/Event.php
Executable file
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\Controller;
|
||||
|
||||
/**
|
||||
* The Event object is created whenever the `$kirby->trigger()`
|
||||
* or `$kirby->apply()` methods are called. It collects all
|
||||
* event information and handles calling the individual hooks.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Lukas Bestle <lukas@getkirby.com>,
|
||||
* Ahmet Bora
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Event
|
||||
{
|
||||
/**
|
||||
* The full event name
|
||||
* (e.g. `page.create:after`)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The event type
|
||||
* (e.g. `page` in `page.create:after`)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The event action
|
||||
* (e.g. `create` in `page.create:after`)
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $action;
|
||||
|
||||
/**
|
||||
* The event state
|
||||
* (e.g. `after` in `page.create:after`)
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The event arguments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $name Full event name
|
||||
* @param array $arguments Associative array of named event arguments
|
||||
*/
|
||||
public function __construct(string $name, array $arguments = [])
|
||||
{
|
||||
// split the event name into `$type.$action:$state`
|
||||
// $action and $state are optional;
|
||||
// if there is more than one dot, $type will be greedy
|
||||
$regex = '/^(?<type>.+?)(?:\.(?<action>[^.]*?))?(?:\:(?<state>.*))?$/';
|
||||
preg_match($regex, $name, $matches, PREG_UNMATCHED_AS_NULL);
|
||||
|
||||
$this->name = $name;
|
||||
$this->type = $matches['type'];
|
||||
$this->action = $matches['action'] ?? null;
|
||||
$this->state = $matches['state'] ?? null;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic caller for event arguments
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $arguments = [])
|
||||
{
|
||||
return $this->argument($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it possible to simply echo
|
||||
* or stringify the entire object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action of the event (e.g. `create`)
|
||||
* or `null` if the event name does not include an action
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function action(): ?string
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific event argument
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function argument(string $name)
|
||||
{
|
||||
if (isset($this->arguments[$name]) === true) {
|
||||
return $this->arguments[$name];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arguments of the event
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function arguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a hook with the event data and returns
|
||||
* the hook's return value
|
||||
*
|
||||
* @param object|null $bind Optional object to bind to the hook function
|
||||
* @param \Closure $hook
|
||||
* @return mixed
|
||||
*/
|
||||
public function call($bind = null, Closure $hook)
|
||||
{
|
||||
// collect the list of possible hook arguments
|
||||
$data = $this->arguments();
|
||||
$data['event'] = $this;
|
||||
|
||||
// magically call the hook with the arguments it requested
|
||||
$hook = new Controller($hook);
|
||||
return $hook->call($bind, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full name of the event
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full list of possible wildcard
|
||||
* event names based on the current event name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function nameWildcards(): array
|
||||
{
|
||||
// if the event is already a wildcard event, no further variation is possible
|
||||
if ($this->type === '*' || $this->action === '*' || $this->state === '*') {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($this->action !== null && $this->state !== null) {
|
||||
// full $type.$action:$state event
|
||||
|
||||
return [
|
||||
$this->type . '.*:' . $this->state,
|
||||
$this->type . '.' . $this->action . ':*',
|
||||
$this->type . '.*:*',
|
||||
'*.' . $this->action . ':' . $this->state,
|
||||
'*.' . $this->action . ':*',
|
||||
'*:' . $this->state,
|
||||
'*'
|
||||
];
|
||||
} elseif ($this->state !== null) {
|
||||
// event without action: $type:$state
|
||||
|
||||
return [
|
||||
$this->type . ':*',
|
||||
'*:' . $this->state,
|
||||
'*'
|
||||
];
|
||||
} elseif ($this->action !== null) {
|
||||
// event without state: $type.$action
|
||||
|
||||
return [
|
||||
$this->type . '.*',
|
||||
'*.' . $this->action,
|
||||
'*'
|
||||
];
|
||||
} else {
|
||||
// event with a simple name
|
||||
|
||||
return ['*'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of the event (e.g. `after`)
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function state(): ?string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event data as array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'arguments' => $this->arguments
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event name as string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the event (e.g. `page`)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a given argument with a new value
|
||||
*
|
||||
* @internal
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function updateArgument(string $name, $value): void
|
||||
{
|
||||
if (array_key_exists($name, $this->arguments) !== true) {
|
||||
throw new InvalidArgumentException('The argument ' . $name . ' does not exist');
|
||||
}
|
||||
|
||||
$this->arguments[$name] = $value;
|
||||
}
|
||||
}
|
||||
@@ -326,14 +326,32 @@ class File extends ModelWithContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique media hash
|
||||
* Check if the file can be read by the current user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
static $readable = [];
|
||||
|
||||
$template = $this->template();
|
||||
|
||||
if (isset($readable[$template]) === true) {
|
||||
return $readable[$template];
|
||||
}
|
||||
|
||||
return $readable[$template] = $this->permissions()->can('read');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique media hash
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaHash(): string
|
||||
{
|
||||
return crc32($this->filename()) . '-' . $this->modifiedFile();
|
||||
return $this->mediaToken() . '-' . $this->modifiedFile();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,6 +365,18 @@ class File extends ModelWithContent
|
||||
return $this->parent()->mediaRoot() . '/' . $this->mediaHash() . '/' . $this->filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-guessable token string for this file
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaToken(): string
|
||||
{
|
||||
$token = $this->kirby()->contentToken($this, $this->id());
|
||||
return substr($token, 0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute Url to the file in the public media folder
|
||||
*
|
||||
@@ -375,12 +405,13 @@ class File extends ModelWithContent
|
||||
*
|
||||
* @param string $format
|
||||
* @param string|null $handler date or strftime
|
||||
* @param string|null $languageCode
|
||||
* @return mixed
|
||||
*/
|
||||
public function modified(string $format = null, string $handler = null)
|
||||
public function modified(string $format = null, string $handler = null, string $languageCode = null)
|
||||
{
|
||||
$file = $this->modifiedFile();
|
||||
$content = $this->modifiedContent();
|
||||
$content = $this->modifiedContent($languageCode);
|
||||
$modified = max($file, $content);
|
||||
|
||||
if (is_null($format) === true) {
|
||||
@@ -396,11 +427,12 @@ class File extends ModelWithContent
|
||||
* Timestamp of the last modification
|
||||
* of the content file
|
||||
*
|
||||
* @param string|null $languageCode
|
||||
* @return int
|
||||
*/
|
||||
protected function modifiedContent(): int
|
||||
protected function modifiedContent(string $languageCode = null): int
|
||||
{
|
||||
return F::modified($this->contentFile());
|
||||
return F::modified($this->contentFile($languageCode));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ trait FileActions
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->commit('changeName', [$this, $name], function ($oldFile, $name) {
|
||||
return $this->commit('changeName', ['file' => $this, 'name' => $name], function ($oldFile, $name) {
|
||||
$newFile = $oldFile->clone([
|
||||
'filename' => $name . '.' . $oldFile->extension(),
|
||||
]);
|
||||
@@ -87,7 +87,7 @@ trait FileActions
|
||||
*/
|
||||
public function changeSort(int $sort)
|
||||
{
|
||||
return $this->commit('changeSort', [$this, $sort], function ($file, $sort) {
|
||||
return $this->commit('changeSort', ['file' => $this, 'position' => $sort], function ($file, $sort) {
|
||||
return $file->save(['sort' => $sort]);
|
||||
});
|
||||
}
|
||||
@@ -108,13 +108,24 @@ trait FileActions
|
||||
*/
|
||||
protected function commit(string $action, array $arguments, Closure $callback)
|
||||
{
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$argumentValues = array_values($arguments);
|
||||
|
||||
$this->rules()->$action(...$argumentValues);
|
||||
$kirby->trigger('file.' . $action . ':before', $arguments);
|
||||
|
||||
$result = $callback(...$argumentValues);
|
||||
|
||||
if ($action === 'create') {
|
||||
$argumentsAfter = ['file' => $result];
|
||||
} elseif ($action === 'delete') {
|
||||
$argumentsAfter = ['status' => $result, 'file' => $old];
|
||||
} else {
|
||||
$argumentsAfter = ['newFile' => $result, 'oldFile' => $old];
|
||||
}
|
||||
$kirby->trigger('file.' . $action . ':after', $argumentsAfter);
|
||||
|
||||
$this->rules()->$action(...$arguments);
|
||||
$kirby->trigger('file.' . $action . ':before', ...$arguments);
|
||||
$result = $callback(...$arguments);
|
||||
$kirby->trigger('file.' . $action . ':after', $result, $old);
|
||||
$kirby->cache('pages')->flush();
|
||||
return $result;
|
||||
}
|
||||
@@ -175,7 +186,7 @@ trait FileActions
|
||||
$file = $file->clone(['content' => $form->strings(true)]);
|
||||
|
||||
// run the hook
|
||||
return $file->commit('create', [$file, $upload], function ($file, $upload) {
|
||||
return $file->commit('create', compact('file', 'upload'), function ($file, $upload) {
|
||||
|
||||
// delete all public versions
|
||||
$file->unpublish();
|
||||
@@ -211,7 +222,7 @@ trait FileActions
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
return $this->commit('delete', [$this], function ($file) {
|
||||
return $this->commit('delete', ['file' => $this], function ($file) {
|
||||
|
||||
// remove all versions in the media folder
|
||||
$file->unpublish();
|
||||
@@ -243,7 +254,7 @@ trait FileActions
|
||||
*/
|
||||
public function publish()
|
||||
{
|
||||
Media::publish($this->root(), $this->mediaRoot());
|
||||
Media::publish($this, $this->mediaRoot());
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -273,7 +284,7 @@ trait FileActions
|
||||
*/
|
||||
public function replace(string $source)
|
||||
{
|
||||
return $this->commit('replace', [$this, new Image($source)], function ($file, $upload) {
|
||||
return $this->commit('replace', ['file' => $this, 'upload' => new Image($source)], function ($file, $upload) {
|
||||
|
||||
// delete all public versions
|
||||
$file->unpublish();
|
||||
@@ -295,7 +306,7 @@ trait FileActions
|
||||
*/
|
||||
public function unpublish()
|
||||
{
|
||||
Media::unpublish($this->parent()->mediaRoot(), $this->filename());
|
||||
Media::unpublish($this->parent()->mediaRoot(), $this);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ class FileBlueprint extends Blueprint
|
||||
'changeName' => null,
|
||||
'create' => null,
|
||||
'delete' => null,
|
||||
'read' => null,
|
||||
'replace' => null,
|
||||
'update' => null,
|
||||
]
|
||||
|
||||
@@ -298,6 +298,6 @@ class Filename
|
||||
'name' => $this->name(),
|
||||
'attributes' => $this->attributesToString('-'),
|
||||
'extension' => $this->extension()
|
||||
]);
|
||||
], '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class Files extends Collection
|
||||
* Sort all given files by the
|
||||
* order in the array
|
||||
*
|
||||
* @param array $files List of filenames
|
||||
* @param array $files List of file ids
|
||||
* @param int $offset Sorting offset
|
||||
* @return self
|
||||
*/
|
||||
|
||||
@@ -126,7 +126,7 @@ trait HasChildren
|
||||
* Finds one or multiple children by id
|
||||
*
|
||||
* @param string ...$arguments
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Pages
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Pages|null
|
||||
*/
|
||||
public function find(...$arguments)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\BadMethodCallException;
|
||||
|
||||
/**
|
||||
* HasMethods
|
||||
*
|
||||
@@ -31,7 +33,13 @@ trait HasMethods
|
||||
*/
|
||||
public function callMethod(string $method, array $args = [])
|
||||
{
|
||||
return static::$methods[$method]->call($this, ...$args);
|
||||
$closure = $this->getMethod($method);
|
||||
|
||||
if ($closure === null) {
|
||||
throw new BadMethodCallException('The method ' . $method . ' does not exist');
|
||||
}
|
||||
|
||||
return $closure->call($this, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,6 +51,29 @@ trait HasMethods
|
||||
*/
|
||||
public function hasMethod(string $method): bool
|
||||
{
|
||||
return isset(static::$methods[$method]) === true;
|
||||
return $this->getMethod($method) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a registered method by name, either from
|
||||
* the current class or from a parent class ordered by
|
||||
* inheritance order (top to bottom)
|
||||
*
|
||||
* @param string $method
|
||||
* @return Closure|null
|
||||
*/
|
||||
protected function getMethod(string $method)
|
||||
{
|
||||
if (isset(static::$methods[$method]) === true) {
|
||||
return static::$methods[$method];
|
||||
}
|
||||
|
||||
foreach (class_parents($this) as $parent) {
|
||||
if (isset($parent::$methods[$method]) === true) {
|
||||
return $parent::$methods[$method];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,32 +25,19 @@ class KirbyTags extends \Kirby\Text\KirbyTags
|
||||
* @param string $text
|
||||
* @param array $data
|
||||
* @param array $options
|
||||
* @param array $hooks
|
||||
* @param \Kirby\Cms\App $app
|
||||
* @return string
|
||||
*/
|
||||
public static function parse(string $text = null, array $data = [], array $options = [], array $hooks = []): string
|
||||
public static function parse(string $text = null, array $data = [], array $options = [], ?App $app = null): string
|
||||
{
|
||||
$text = static::hooks($hooks['kirbytags:before'] ?? [], $text, $data, $options);
|
||||
if ($app !== null) {
|
||||
$text = $app->apply('kirbytags:before', compact('text', 'data', 'options'), 'text');
|
||||
}
|
||||
|
||||
$text = parent::parse($text, $data, $options);
|
||||
$text = static::hooks($hooks['kirbytags:after'] ?? [], $text, $data, $options);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given hooks and returns the
|
||||
* modified text
|
||||
*
|
||||
* @param array $hooks
|
||||
* @param string $text
|
||||
* @param array $data
|
||||
* @param array $options
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function hooks(array $hooks, string $text = null, array $data, array $options): ?string
|
||||
{
|
||||
foreach ($hooks as $hook) {
|
||||
$text = $hook->call($data['kirby'], $text, $data, $options);
|
||||
if ($app !== null) {
|
||||
$text = $app->apply('kirbytags:after', compact('text', 'data', 'options'), 'text');
|
||||
}
|
||||
|
||||
return $text;
|
||||
|
||||
@@ -94,7 +94,7 @@ class Languages extends Collection
|
||||
$files = glob(App::instance()->root('languages') . '/*.php');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$props = include $file;
|
||||
$props = F::load($file);
|
||||
|
||||
if (is_array($props) === true) {
|
||||
// inject the language code from the filename if it does not exist
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Kirby\Cms;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -41,9 +42,15 @@ class Media
|
||||
// this should work for all original files
|
||||
if ($file = $model->file($filename)) {
|
||||
|
||||
// the media hash is outdated. redirect to the correct url
|
||||
// check if the request contained an outdated media hash
|
||||
if ($file->mediaHash() !== $hash) {
|
||||
return Response::redirect($file->mediaUrl(), 307);
|
||||
// if at least the token was correct, redirect
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// send the file to the browser
|
||||
@@ -57,18 +64,18 @@ class Media
|
||||
/**
|
||||
* Copy the file to the final media folder location
|
||||
*
|
||||
* @param string $src
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string $dest
|
||||
* @return bool
|
||||
*/
|
||||
public static function publish(string $src, string $dest): bool
|
||||
public static function publish(File $file, string $dest): bool
|
||||
{
|
||||
$filename = basename($src);
|
||||
$src = $file->root();
|
||||
$version = dirname($dest);
|
||||
$directory = dirname($version);
|
||||
|
||||
// unpublish all files except stuff in the version folder
|
||||
Media::unpublish($directory, $filename, $version);
|
||||
Media::unpublish($directory, $file, $version);
|
||||
|
||||
// copy/overwrite the file to the dest folder
|
||||
return F::copy($src, $dest, true);
|
||||
@@ -125,21 +132,25 @@ class Media
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all versions of the given filename
|
||||
* Deletes all versions of the given file
|
||||
* within the parent directory
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $filename
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string $ignore
|
||||
* @return bool
|
||||
*/
|
||||
public static function unpublish(string $directory, string $filename, string $ignore = null): bool
|
||||
public static function unpublish(string $directory, File $file, string $ignore = null): bool
|
||||
{
|
||||
if (is_dir($directory) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$versions = glob($directory . '/' . crc32($filename) . '*', GLOB_ONLYDIR);
|
||||
// get both old and new versions (pre and post Kirby 3.4.0)
|
||||
$versions = array_merge(
|
||||
glob($directory . '/' . crc32($file->filename()) . '-*', GLOB_ONLYDIR),
|
||||
glob($directory . '/' . $file->mediaToken() . '-*', GLOB_ONLYDIR)
|
||||
);
|
||||
|
||||
// delete all versions of the file
|
||||
foreach ($versions as $version) {
|
||||
|
||||
@@ -334,6 +334,11 @@ abstract class ModelWithContent extends Model
|
||||
}
|
||||
|
||||
if (is_string($settings) === true) {
|
||||
// use defined icon in blueprint
|
||||
if ($settings === 'icon') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$settings = [
|
||||
'query' => $settings
|
||||
];
|
||||
@@ -448,11 +453,15 @@ abstract class ModelWithContent extends Model
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = Str::query($query, [
|
||||
'kirby' => $this->kirby(),
|
||||
'site' => is_a($this, 'Kirby\Cms\Site') ? $this : $this->site(),
|
||||
static::CLASS_ALIAS => $this
|
||||
]);
|
||||
try {
|
||||
$result = Str::query($query, [
|
||||
'kirby' => $this->kirby(),
|
||||
'site' => is_a($this, 'Kirby\Cms\Site') ? $this : $this->site(),
|
||||
static::CLASS_ALIAS => $this
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($expect !== null && is_a($result, $expect) !== true) {
|
||||
return null;
|
||||
@@ -611,19 +620,21 @@ abstract class ModelWithContent extends Model
|
||||
* String template builder
|
||||
*
|
||||
* @param string|null $template
|
||||
* @param array $data
|
||||
* @param string $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* @return string
|
||||
*/
|
||||
public function toString(string $template = null): string
|
||||
public function toString(string $template = null, array $data = [], string $fallback = ''): string
|
||||
{
|
||||
if ($template === null) {
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
$result = Str::template($template, [
|
||||
$result = Str::template($template, array_replace([
|
||||
'kirby' => $this->kirby(),
|
||||
'site' => is_a($this, 'Kirby\Cms\Site') ? $this : $this->site(),
|
||||
static::CLASS_ALIAS => $this
|
||||
]);
|
||||
], $data), $fallback);
|
||||
|
||||
return $result;
|
||||
}
|
||||
@@ -691,7 +702,8 @@ abstract class ModelWithContent extends Model
|
||||
}
|
||||
}
|
||||
|
||||
return $this->commit('update', [$this, $form->data(), $form->strings(), $languageCode], function ($model, $values, $strings, $languageCode) {
|
||||
$arguments = [static::CLASS_ALIAS => $this, 'values' => $form->data(), 'strings' => $form->strings(), 'languageCode' => $languageCode];
|
||||
return $this->commit('update', $arguments, function ($model, $values, $strings, $languageCode) {
|
||||
// save updated values
|
||||
$model = $model->save($strings, $languageCode, true);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Toolkit\A;
|
||||
@@ -344,7 +345,31 @@ class Page extends ModelWithContent
|
||||
]);
|
||||
|
||||
// call the template controller if there's one.
|
||||
return array_merge($kirby->controller($this->template()->name(), $data, $contentType), $data);
|
||||
$controllerData = $kirby->controller($this->template()->name(), $data, $contentType);
|
||||
|
||||
// merge controller data with original data safely
|
||||
if (empty($controllerData) === false) {
|
||||
$classes = [
|
||||
'kirby' => 'Kirby\Cms\App',
|
||||
'site' => 'Kirby\Cms\Site',
|
||||
'pages' => 'Kirby\Cms\Pages',
|
||||
'page' => 'Kirby\Cms\Page'
|
||||
];
|
||||
|
||||
foreach ($controllerData as $key => $value) {
|
||||
if (array_key_exists($key, $classes) === true) {
|
||||
if (is_a($value, $classes[$key]) === true) {
|
||||
$data[$key] = $value;
|
||||
} else {
|
||||
throw new InvalidArgumentException('The returned variable "' . $key . '" from the controller "' . $this->template()->name() . '" is not of the required type "' . $classes[$key] . '"');
|
||||
}
|
||||
} else {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,6 +478,20 @@ class Page extends ModelWithContent
|
||||
return new static($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to this page,
|
||||
* wrapper for the `go()` helper
|
||||
*
|
||||
* @since 3.4.0
|
||||
*
|
||||
* @param array $options Options for `Kirby\Http\Uri` to create URL parts
|
||||
* @param int $code HTTP status code
|
||||
*/
|
||||
public function go(array $options = [], int $code = 302)
|
||||
{
|
||||
go($this->url($options), $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the intended template
|
||||
* for the page exists.
|
||||
@@ -889,11 +928,16 @@ class Page extends ModelWithContent
|
||||
*
|
||||
* @param string $format
|
||||
* @param string|null $handler
|
||||
* @param string|null $languageCode
|
||||
* @return int|string
|
||||
*/
|
||||
public function modified(string $format = null, string $handler = null)
|
||||
public function modified(string $format = null, string $handler = null, string $languageCode = null)
|
||||
{
|
||||
return F::modified($this->contentFile(), $format, $handler ?? $this->kirby()->option('date.handler', 'date'));
|
||||
return F::modified(
|
||||
$this->contentFile($languageCode),
|
||||
$format,
|
||||
$handler ?? $this->kirby()->option('date.handler', 'date')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1448,7 +1492,7 @@ class Page extends ModelWithContent
|
||||
*/
|
||||
protected function token(): string
|
||||
{
|
||||
return sha1($this->id() . $this->template());
|
||||
return $this->kirby()->contentToken($this, $this->id() . $this->template());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,7 @@ trait PageActions
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->commit('changeNum', [$this, $num], function ($oldPage, $num) {
|
||||
return $this->commit('changeNum', ['page' => $this, 'num' => $num], function ($oldPage, $num) {
|
||||
$newPage = $oldPage->clone([
|
||||
'num' => $num,
|
||||
'dirname' => null,
|
||||
@@ -97,7 +97,8 @@ trait PageActions
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->commit('changeSlug', [$this, $slug, $languageCode = null], function ($oldPage, $slug) {
|
||||
$arguments = ['page' => $this, 'slug' => $slug, 'languageCode' => null];
|
||||
return $this->commit('changeSlug', $arguments, function ($oldPage, $slug) {
|
||||
$newPage = $oldPage->clone([
|
||||
'slug' => $slug,
|
||||
'dirname' => null,
|
||||
@@ -151,7 +152,8 @@ trait PageActions
|
||||
throw new InvalidArgumentException('Use the changeSlug method to change the slug for the default language');
|
||||
}
|
||||
|
||||
return $this->commit('changeSlug', [$this, $slug, $languageCode], function ($page, $slug, $languageCode) {
|
||||
$arguments = ['page' => $this, 'slug' => $slug, 'languageCode' => $languageCode];
|
||||
return $this->commit('changeSlug', $arguments, function ($page, $slug, $languageCode) {
|
||||
// remove the slug if it's the same as the folder name
|
||||
if ($slug === $page->uid()) {
|
||||
$slug = null;
|
||||
@@ -185,7 +187,8 @@ trait PageActions
|
||||
|
||||
protected function changeStatusToDraft()
|
||||
{
|
||||
$page = $this->commit('changeStatus', [$this, 'draft', null], function ($page) {
|
||||
$arguments = ['page' => $this, 'status' => 'draft', 'position' => null];
|
||||
$page = $this->commit('changeStatus', $arguments, function ($page) {
|
||||
return $page->unpublish();
|
||||
});
|
||||
|
||||
@@ -206,7 +209,8 @@ trait PageActions
|
||||
return $this;
|
||||
}
|
||||
|
||||
$page = $this->commit('changeStatus', [$this, 'listed', $num], function ($page, $status, $position) {
|
||||
$arguments = ['page' => $this, 'status' => 'listed', 'position' => $num];
|
||||
$page = $this->commit('changeStatus', $arguments, function ($page, $status, $position) {
|
||||
return $page->publish()->changeNum($position);
|
||||
});
|
||||
|
||||
@@ -226,7 +230,8 @@ trait PageActions
|
||||
return $this;
|
||||
}
|
||||
|
||||
$page = $this->commit('changeStatus', [$this, 'unlisted', null], function ($page) {
|
||||
$arguments = ['page' => $this, 'status' => 'unlisted', 'position' => null];
|
||||
$page = $this->commit('changeStatus', $arguments, function ($page) {
|
||||
return $page->publish()->changeNum(null);
|
||||
});
|
||||
|
||||
@@ -243,11 +248,11 @@ trait PageActions
|
||||
*/
|
||||
public function changeTemplate(string $template)
|
||||
{
|
||||
if ($template === $this->template()->name()) {
|
||||
if ($template === $this->intendedTemplate()->name()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->commit('changeTemplate', [$this, $template], function ($oldPage, $template) {
|
||||
return $this->commit('changeTemplate', ['page' => $this, 'template' => $template], function ($oldPage, $template) {
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
$newPage = $this->clone([
|
||||
'template' => $template
|
||||
@@ -290,7 +295,8 @@ trait PageActions
|
||||
*/
|
||||
public function changeTitle(string $title, string $languageCode = null)
|
||||
{
|
||||
return $this->commit('changeTitle', [$this, $title, $languageCode], function ($page, $title, $languageCode) {
|
||||
$arguments = ['page' => $this, 'title' => $title, 'languageCode' => $languageCode];
|
||||
return $this->commit('changeTitle', $arguments, function ($page, $title, $languageCode) {
|
||||
return $page->save(['title' => $title], $languageCode);
|
||||
});
|
||||
}
|
||||
@@ -311,13 +317,27 @@ trait PageActions
|
||||
*/
|
||||
protected function commit(string $action, array $arguments, Closure $callback)
|
||||
{
|
||||
$old = $this->hardcopy();
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$argumentValues = array_values($arguments);
|
||||
|
||||
$this->rules()->$action(...$arguments);
|
||||
$this->kirby()->trigger('page.' . $action . ':before', ...$arguments);
|
||||
$result = $callback(...$arguments);
|
||||
$this->kirby()->trigger('page.' . $action . ':after', $result, $old);
|
||||
$this->kirby()->cache('pages')->flush();
|
||||
$this->rules()->$action(...$argumentValues);
|
||||
$kirby->trigger('page.' . $action . ':before', $arguments);
|
||||
|
||||
$result = $callback(...$argumentValues);
|
||||
|
||||
if ($action === 'create') {
|
||||
$argumentsAfter = ['page' => $result];
|
||||
} elseif ($action === 'duplicate') {
|
||||
$argumentsAfter = ['duplicatePage' => $result];
|
||||
} elseif ($action === 'delete') {
|
||||
$argumentsAfter = ['status' => $result, 'page' => $old];
|
||||
} else {
|
||||
$argumentsAfter = ['newPage' => $result, 'oldPage' => $old];
|
||||
}
|
||||
$kirby->trigger('page.' . $action . ':after', $argumentsAfter);
|
||||
|
||||
$kirby->cache('pages')->flush();
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -356,7 +376,9 @@ trait PageActions
|
||||
'slug' => $slug,
|
||||
]);
|
||||
|
||||
$ignore = [];
|
||||
$ignore = [
|
||||
$this->kirby()->locks()->file($this)
|
||||
];
|
||||
|
||||
// don't copy files
|
||||
if ($files === false) {
|
||||
@@ -375,7 +397,7 @@ trait PageActions
|
||||
// remove all translated slugs
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
foreach ($this->kirby()->languages() as $language) {
|
||||
if ($language->isDefault() === false) {
|
||||
if ($language->isDefault() === false && $copy->translation($language)->exists() === true) {
|
||||
$copy = $copy->save(['slug' => null], $language->code());
|
||||
}
|
||||
}
|
||||
@@ -416,7 +438,7 @@ trait PageActions
|
||||
$page = $page->clone(['content' => $form->strings(true)]);
|
||||
|
||||
// run the hooks and creation action
|
||||
$page = $page->commit('create', [$page, $props], function ($page, $props) {
|
||||
$page = $page->commit('create', ['page' => $page, 'input' => $props], function ($page, $props) {
|
||||
|
||||
// always create pages in the default language
|
||||
if ($page->kirby()->multilang() === true) {
|
||||
@@ -461,7 +483,8 @@ trait PageActions
|
||||
'site' => $this->site(),
|
||||
]);
|
||||
|
||||
return static::create($props);
|
||||
$modelClass = Page::$models[$props['template']] ?? Page::class;
|
||||
return $modelClass::create($props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -520,7 +543,7 @@ trait PageActions
|
||||
'kirby' => $app,
|
||||
'page' => $app->page($this->id()),
|
||||
'site' => $app->site(),
|
||||
]);
|
||||
], '');
|
||||
|
||||
return (int)$template;
|
||||
}
|
||||
@@ -534,7 +557,7 @@ trait PageActions
|
||||
*/
|
||||
public function delete(bool $force = false): bool
|
||||
{
|
||||
return $this->commit('delete', [$this, $force], function ($page, $force) {
|
||||
return $this->commit('delete', ['page' => $this, 'force' => $force], function ($page, $force) {
|
||||
|
||||
// delete all files individually
|
||||
foreach ($page->files() as $file) {
|
||||
@@ -591,7 +614,8 @@ trait PageActions
|
||||
// create the slug for the duplicate
|
||||
$slug = Str::slug($slug ?? $this->slug() . '-copy');
|
||||
|
||||
return $this->commit('duplicate', [$this, $slug, $options], function ($page, $slug, $options) {
|
||||
$arguments = ['originalPage' => $this, 'input' => $slug, 'options' => $options];
|
||||
return $this->commit('duplicate', $arguments, function ($page, $slug, $options) {
|
||||
return $this->copy([
|
||||
'parent' => $this->parent(),
|
||||
'slug' => $slug,
|
||||
|
||||
@@ -238,11 +238,11 @@ class Pages extends Collection
|
||||
public function findByIdRecursive(string $id, string $startAt = null, bool $multiLang = false)
|
||||
{
|
||||
$path = explode('/', $id);
|
||||
$collection = $this;
|
||||
$item = null;
|
||||
$query = $startAt;
|
||||
|
||||
foreach ($path as $key) {
|
||||
$collection = $item ? $item->children() : $this;
|
||||
$query = ltrim($query . '/' . $key, '/');
|
||||
$item = $collection->get($query) ?? null;
|
||||
|
||||
@@ -253,8 +253,6 @@ class Pages extends Collection
|
||||
if ($item === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$collection = $item->children();
|
||||
}
|
||||
|
||||
return $item;
|
||||
@@ -340,9 +338,12 @@ class Pages extends Collection
|
||||
|
||||
foreach ($this->data as $pageKey => $page) {
|
||||
$this->index->data[$pageKey] = $page;
|
||||
$index = $page->index($drafts);
|
||||
|
||||
foreach ($page->index($drafts) as $childKey => $child) {
|
||||
$this->index->data[$childKey] = $child;
|
||||
if ($index) {
|
||||
foreach ($index as $childKey => $child) {
|
||||
$this->index->data[$childKey] = $child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use Kirby\Toolkit\Pagination as BasePagination;
|
||||
class Pagination extends BasePagination
|
||||
{
|
||||
/**
|
||||
* Pagination method (param or query)
|
||||
* Pagination method (param, query, none)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
@@ -82,7 +82,7 @@ class Pagination extends BasePagination
|
||||
|
||||
if ($params['method'] === 'query') {
|
||||
$params['page'] = $params['page'] ?? $params['url']->query()->get($params['variable']);
|
||||
} else {
|
||||
} elseif ($params['method'] === 'param') {
|
||||
$params['page'] = $params['page'] ?? $params['url']->params()->get($params['variable']);
|
||||
}
|
||||
|
||||
@@ -153,8 +153,10 @@ class Pagination extends BasePagination
|
||||
|
||||
if ($this->method === 'query') {
|
||||
$url->query->$variable = $pageValue;
|
||||
} else {
|
||||
} elseif ($this->method === 'param') {
|
||||
$url->params->$variable = $pageValue;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $url->toString();
|
||||
|
||||
@@ -89,7 +89,7 @@ class Panel
|
||||
go($kirby->url('index') . '/' . $kirby->path());
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
die('The panel assets cannot be installed properly. Please check permissions of your media folder.');
|
||||
die('The Panel assets cannot be installed properly. ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// get the uri object for the panel url
|
||||
|
||||
@@ -17,6 +17,8 @@ use Kirby\Exception\InvalidArgumentException;
|
||||
*/
|
||||
class Permissions
|
||||
{
|
||||
public static $extendedActions = [];
|
||||
|
||||
protected $actions = [
|
||||
'access' => [
|
||||
'panel' => true,
|
||||
@@ -28,6 +30,7 @@ class Permissions
|
||||
'changeName' => true,
|
||||
'create' => true,
|
||||
'delete' => true,
|
||||
'read' => true,
|
||||
'replace' => true,
|
||||
'update' => true
|
||||
],
|
||||
@@ -75,6 +78,15 @@ class Permissions
|
||||
|
||||
public function __construct($settings = [])
|
||||
{
|
||||
// dynamically register the extended actions
|
||||
foreach (static::$extendedActions as $key => $actions) {
|
||||
if (isset($this->actions[$key]) === true) {
|
||||
throw new InvalidArgumentException('The action ' . $key . ' is already a core action');
|
||||
}
|
||||
|
||||
$this->actions[$key] = $actions;
|
||||
}
|
||||
|
||||
if (is_array($settings) === true) {
|
||||
return $this->setCategories($settings);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The Search class extracts the
|
||||
* search logic from collections, to
|
||||
@@ -31,100 +29,15 @@ class Search
|
||||
/**
|
||||
* Native search method to search for anything within the collection
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param \Kirby\Cms\Collection $collection
|
||||
* @param string $query
|
||||
* @param mixed $params
|
||||
* @return \Kirby\Cms\Collection|bool
|
||||
*/
|
||||
public static function collection(Collection $collection, string $query = null, $params = [])
|
||||
{
|
||||
if (empty(trim($query)) === true) {
|
||||
return $collection->limit(0);
|
||||
}
|
||||
|
||||
if (is_string($params) === true) {
|
||||
$params = ['fields' => Str::split($params, '|')];
|
||||
}
|
||||
|
||||
$defaults = [
|
||||
'fields' => [],
|
||||
'minlength' => 2,
|
||||
'score' => [],
|
||||
'words' => false,
|
||||
];
|
||||
|
||||
$options = array_merge($defaults, $params);
|
||||
$collection = clone $collection;
|
||||
$searchwords = preg_replace('/(\s)/u', ',', $query);
|
||||
$searchwords = Str::split($searchwords, ',', $options['minlength']);
|
||||
$lowerQuery = mb_strtolower($query);
|
||||
|
||||
if (empty($options['stopwords']) === false) {
|
||||
$searchwords = array_diff($searchwords, $options['stopwords']);
|
||||
}
|
||||
|
||||
$searchwords = array_map(function ($value) use ($options) {
|
||||
return $options['words'] ? '\b' . preg_quote($value) . '\b' : preg_quote($value);
|
||||
}, $searchwords);
|
||||
|
||||
$preg = '!(' . implode('|', $searchwords) . ')!i';
|
||||
$results = $collection->filter(function ($item) use ($query, $preg, $options, $lowerQuery) {
|
||||
$data = $item->content()->toArray();
|
||||
$keys = array_keys($data);
|
||||
$keys[] = 'id';
|
||||
|
||||
if (is_a($item, 'Kirby\Cms\User') === true) {
|
||||
$keys[] = 'name';
|
||||
$keys[] = 'email';
|
||||
$keys[] = 'role';
|
||||
} elseif (is_a($item, 'Kirby\Cms\Page') === true) {
|
||||
// apply the default score for pages
|
||||
$options['score'] = array_merge([
|
||||
'id' => 64,
|
||||
'title' => 64,
|
||||
], $options['score']);
|
||||
}
|
||||
|
||||
if (empty($options['fields']) === false) {
|
||||
$fields = array_map('strtolower', $options['fields']);
|
||||
$keys = array_intersect($keys, $fields);
|
||||
}
|
||||
|
||||
$item->searchHits = 0;
|
||||
$item->searchScore = 0;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$score = $options['score'][$key] ?? 1;
|
||||
$value = $data[$key] ?? (string)$item->$key();
|
||||
|
||||
$lowerValue = mb_strtolower($value);
|
||||
|
||||
// check for exact matches
|
||||
if ($lowerQuery == $lowerValue) {
|
||||
$item->searchScore += 16 * $score;
|
||||
$item->searchHits += 1;
|
||||
|
||||
// check for exact beginning matches
|
||||
} elseif (Str::startsWith($lowerValue, $lowerQuery) === true) {
|
||||
$item->searchScore += 8 * $score;
|
||||
$item->searchHits += 1;
|
||||
|
||||
// check for exact query matches
|
||||
} elseif ($matches = preg_match_all('!' . preg_quote($query) . '!i', $value, $r)) {
|
||||
$item->searchScore += 2 * $score;
|
||||
$item->searchHits += $matches;
|
||||
}
|
||||
|
||||
// check for any match
|
||||
if ($matches = preg_match_all($preg, $value, $r)) {
|
||||
$item->searchHits += $matches;
|
||||
$item->searchScore += $matches * $score;
|
||||
}
|
||||
}
|
||||
|
||||
return $item->searchHits > 0 ? true : false;
|
||||
});
|
||||
|
||||
return $results->sortBy('searchScore', 'desc');
|
||||
$kirby = App::instance();
|
||||
return $kirby->component('search')($kirby, $collection, $query, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,13 +31,17 @@ trait SiteActions
|
||||
*/
|
||||
protected function commit(string $action, array $arguments, Closure $callback)
|
||||
{
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$argumentValues = array_values($arguments);
|
||||
|
||||
$this->rules()->$action(...$argumentValues);
|
||||
$kirby->trigger('site.' . $action . ':before', $arguments);
|
||||
|
||||
$result = $callback(...$argumentValues);
|
||||
|
||||
$kirby->trigger('site.' . $action . ':after', ['newSite' => $result, 'oldSite' => $old]);
|
||||
|
||||
$this->rules()->$action(...$arguments);
|
||||
$kirby->trigger('site.' . $action . ':before', ...$arguments);
|
||||
$result = $callback(...$arguments);
|
||||
$kirby->trigger('site.' . $action . ':after', $result, $old);
|
||||
$kirby->cache('pages')->flush();
|
||||
return $result;
|
||||
}
|
||||
@@ -51,7 +55,8 @@ trait SiteActions
|
||||
*/
|
||||
public function changeTitle(string $title, string $languageCode = null)
|
||||
{
|
||||
return $this->commit('changeTitle', [$this, $title, $languageCode], function ($site, $title, $languageCode) {
|
||||
$arguments = ['site' => $this, 'title' => $title, 'languageCode' => $languageCode];
|
||||
return $this->commit('changeTitle', $arguments, function ($site, $title, $languageCode) {
|
||||
return $site->save(['title' => $title], $languageCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Kirby\Exception\PermissionException;
|
||||
use Kirby\Http\Remote;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Http\Url;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
@@ -281,7 +282,9 @@ class System
|
||||
* Loads the license file and returns
|
||||
* the license information if available
|
||||
*
|
||||
* @return string|false
|
||||
* @return string|bool License key or `false` if the current user has
|
||||
* permissions for access.settings, otherwise just a
|
||||
* boolean that tells whether a valid license is active
|
||||
*/
|
||||
public function license()
|
||||
{
|
||||
@@ -326,7 +329,14 @@ class System
|
||||
return false;
|
||||
}
|
||||
|
||||
return $license['license'];
|
||||
// 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) {
|
||||
return $license['license'];
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,13 +432,17 @@ class System
|
||||
*/
|
||||
public function server(): bool
|
||||
{
|
||||
$servers = [
|
||||
'apache',
|
||||
'caddy',
|
||||
'litespeed',
|
||||
'nginx',
|
||||
'php'
|
||||
];
|
||||
if ($servers = $this->app->option('servers')) {
|
||||
$servers = A::wrap($servers);
|
||||
} else {
|
||||
$servers = [
|
||||
'apache',
|
||||
'caddy',
|
||||
'litespeed',
|
||||
'nginx',
|
||||
'php'
|
||||
];
|
||||
}
|
||||
|
||||
$software = $_SERVER['SERVER_SOFTWARE'] ?? null;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Http\Url as BaseUrl;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The `Url` class extends the
|
||||
@@ -61,36 +60,10 @@ class Url extends BaseUrl
|
||||
*/
|
||||
public static function to(string $path = null, $options = null): string
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$language = null;
|
||||
$kirby = App::instance();
|
||||
|
||||
// get language from simple string option
|
||||
if (is_string($options) === true) {
|
||||
$language = $options;
|
||||
$options = null;
|
||||
}
|
||||
|
||||
// get language from array
|
||||
if (is_array($options) === true && isset($options['language']) === true) {
|
||||
$language = $options['language'];
|
||||
unset($options['language']);
|
||||
}
|
||||
|
||||
// get a language url for the linked page, if the page can be found
|
||||
if ($kirby->multilang() === true) {
|
||||
$parts = Str::split($path, '#');
|
||||
|
||||
if ($page = page($parts[0] ?? null)) {
|
||||
$path = $page->url($language);
|
||||
|
||||
if (isset($parts[1]) === true) {
|
||||
$path .= '#' . $parts[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $kirby->component('url')($kirby, $path, $options, function (string $path = null, $options = null) {
|
||||
return parent::to($path, $options);
|
||||
return $kirby->component('url')($kirby, $path, $options, function (string $path = null, $options = null) use ($kirby) {
|
||||
return $kirby->nativeComponent('url')($kirby, $path, $options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,13 +426,13 @@ class User extends ModelWithContent
|
||||
|
||||
$session = $this->sessionFromOptions($session);
|
||||
|
||||
$kirby->trigger('user.login:before', $this, $session);
|
||||
$kirby->trigger('user.login:before', ['user' => $this, 'session' => $session]);
|
||||
|
||||
$session->regenerateToken(); // privilege change
|
||||
$session->data()->set('user.id', $this->id());
|
||||
$this->kirby()->auth()->setUser($this);
|
||||
|
||||
$kirby->trigger('user.login:after', $this, $session);
|
||||
$kirby->trigger('user.login:after', ['user' => $this, 'session' => $session]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,7 +446,7 @@ class User extends ModelWithContent
|
||||
$kirby = $this->kirby();
|
||||
$session = $this->sessionFromOptions($session);
|
||||
|
||||
$kirby->trigger('user.logout:before', $this, $session);
|
||||
$kirby->trigger('user.logout:before', ['user' => $this, 'session' => $session]);
|
||||
|
||||
// remove the user from the session for future requests
|
||||
$session->data()->remove('user.id');
|
||||
@@ -458,12 +458,12 @@ class User extends ModelWithContent
|
||||
// session is now empty, we might as well destroy it
|
||||
$session->destroy();
|
||||
|
||||
$kirby->trigger('user.logout:after', $this, null);
|
||||
$kirby->trigger('user.logout:after', ['user' => $this, 'session' => null]);
|
||||
} else {
|
||||
// privilege change
|
||||
$session->regenerateToken();
|
||||
|
||||
$kirby->trigger('user.logout:after', $this, $session);
|
||||
$kirby->trigger('user.logout:after', ['user' => $this, 'session' => $session]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,11 +515,12 @@ class User extends ModelWithContent
|
||||
*
|
||||
* @param string $format
|
||||
* @param string|null $handler
|
||||
* @param string|null $languageCode
|
||||
* @return int|string
|
||||
*/
|
||||
public function modified(string $format = 'U', string $handler = null)
|
||||
public function modified(string $format = 'U', string $handler = null, string $languageCode = null)
|
||||
{
|
||||
$modifiedContent = F::modified($this->contentFile());
|
||||
$modifiedContent = F::modified($this->contentFile($languageCode));
|
||||
$modifiedIndex = F::modified($this->root() . '/index.php');
|
||||
$modifiedTotal = max([$modifiedContent, $modifiedIndex]);
|
||||
$handler = $handler ?? $this->kirby()->option('date.handler', 'date');
|
||||
@@ -682,6 +683,41 @@ class User extends ModelWithContent
|
||||
return $this->role = Role::nobody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available roles
|
||||
* for this user, that can be selected
|
||||
* by the authenticated user
|
||||
*
|
||||
* @return \Kirby\Cms\Roles
|
||||
*/
|
||||
public function roles()
|
||||
{
|
||||
$kirby = $this->kirby();
|
||||
$roles = $kirby->roles();
|
||||
|
||||
// a collection with just the one role of the user
|
||||
$myRole = $roles->filterBy('id', $this->role()->id());
|
||||
|
||||
// if there's an authenticated user …
|
||||
if ($user = $kirby->user()) {
|
||||
|
||||
// admin users can select pretty much any role
|
||||
if ($user->isAdmin() === true) {
|
||||
// except if the user is the last admin
|
||||
if ($this->isLastAdmin() === true) {
|
||||
// in which case they have to stay admin
|
||||
return $myRole;
|
||||
}
|
||||
|
||||
// return all roles for mighty admins
|
||||
return $roles;
|
||||
}
|
||||
}
|
||||
|
||||
// any other user can only keep their role
|
||||
return $myRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* The absolute path to the user directory
|
||||
*
|
||||
@@ -844,15 +880,17 @@ class User extends ModelWithContent
|
||||
* String template builder
|
||||
*
|
||||
* @param string|null $template
|
||||
* @param array|null $data
|
||||
* @param string $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* @return string
|
||||
*/
|
||||
public function toString(string $template = null): string
|
||||
public function toString(string $template = null, array $data = [], string $fallback = ''): string
|
||||
{
|
||||
if ($template === null) {
|
||||
$template = $this->email();
|
||||
}
|
||||
|
||||
return parent::toString($template);
|
||||
return parent::toString($template, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,7 @@ trait UserActions
|
||||
*/
|
||||
public function changeEmail(string $email)
|
||||
{
|
||||
return $this->commit('changeEmail', [$this, $email], function ($user, $email) {
|
||||
return $this->commit('changeEmail', ['user' => $this, 'email' => $email], function ($user, $email) {
|
||||
$user = $user->clone([
|
||||
'email' => $email
|
||||
]);
|
||||
@@ -50,7 +50,7 @@ trait UserActions
|
||||
*/
|
||||
public function changeLanguage(string $language)
|
||||
{
|
||||
return $this->commit('changeLanguage', [$this, $language], function ($user, $language) {
|
||||
return $this->commit('changeLanguage', ['user' => $this, 'language' => $language], function ($user, $language) {
|
||||
$user = $user->clone([
|
||||
'language' => $language,
|
||||
]);
|
||||
@@ -71,7 +71,7 @@ trait UserActions
|
||||
*/
|
||||
public function changeName(string $name)
|
||||
{
|
||||
return $this->commit('changeName', [$this, $name], function ($user, $name) {
|
||||
return $this->commit('changeName', ['user' => $this, 'name' => $name], function ($user, $name) {
|
||||
$user = $user->clone([
|
||||
'name' => $name
|
||||
]);
|
||||
@@ -92,7 +92,7 @@ trait UserActions
|
||||
*/
|
||||
public function changePassword(string $password)
|
||||
{
|
||||
return $this->commit('changePassword', [$this, $password], function ($user, $password) {
|
||||
return $this->commit('changePassword', ['user' => $this, 'password' => $password], function ($user, $password) {
|
||||
$user = $user->clone([
|
||||
'password' => $password = User::hashPassword($password)
|
||||
]);
|
||||
@@ -111,7 +111,7 @@ trait UserActions
|
||||
*/
|
||||
public function changeRole(string $role)
|
||||
{
|
||||
return $this->commit('changeRole', [$this, $role], function ($user, $role) {
|
||||
return $this->commit('changeRole', ['user' => $this, 'role' => $role], function ($user, $role) {
|
||||
$user = $user->clone([
|
||||
'role' => $role,
|
||||
]);
|
||||
@@ -144,13 +144,25 @@ trait UserActions
|
||||
throw new PermissionException('The Kirby user cannot be changed');
|
||||
}
|
||||
|
||||
$old = $this->hardcopy();
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$argumentValues = array_values($arguments);
|
||||
|
||||
$this->rules()->$action(...$arguments);
|
||||
$this->kirby()->trigger('user.' . $action . ':before', ...$arguments);
|
||||
$result = $callback(...$arguments);
|
||||
$this->kirby()->trigger('user.' . $action . ':after', $result, $old);
|
||||
$this->kirby()->cache('pages')->flush();
|
||||
$this->rules()->$action(...$argumentValues);
|
||||
$kirby->trigger('user.' . $action . ':before', $arguments);
|
||||
|
||||
$result = $callback(...$argumentValues);
|
||||
|
||||
if ($action === 'create') {
|
||||
$argumentsAfter = ['user' => $result];
|
||||
} elseif ($action === 'delete') {
|
||||
$argumentsAfter = ['status' => $result, 'user' => $old];
|
||||
} else {
|
||||
$argumentsAfter = ['newUser' => $result, 'oldUser' => $old];
|
||||
}
|
||||
$kirby->trigger('user.' . $action . ':after', $argumentsAfter);
|
||||
|
||||
$kirby->cache('pages')->flush();
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -181,7 +193,7 @@ trait UserActions
|
||||
$user = $user->clone(['content' => $form->strings(true)]);
|
||||
|
||||
// run the hook
|
||||
return $user->commit('create', [$user, $props], function ($user, $props) {
|
||||
return $user->commit('create', ['user' => $user, 'input' => $props], function ($user, $props) {
|
||||
$user->writeCredentials([
|
||||
'email' => $user->email(),
|
||||
'language' => $user->language(),
|
||||
@@ -231,7 +243,7 @@ trait UserActions
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
return $this->commit('delete', [$this], function ($user) {
|
||||
return $this->commit('delete', ['user' => $this], function ($user) {
|
||||
if ($user->exists() === false) {
|
||||
return true;
|
||||
}
|
||||
@@ -258,8 +270,10 @@ trait UserActions
|
||||
*/
|
||||
protected function readCredentials(): array
|
||||
{
|
||||
if (file_exists($this->root() . '/index.php') === true) {
|
||||
$credentials = require $this->root() . '/index.php';
|
||||
$path = $this->root() . '/index.php';
|
||||
|
||||
if (is_file($path) === true) {
|
||||
$credentials = F::load($path);
|
||||
|
||||
return is_array($credentials) === false ? [] : $credentials;
|
||||
} else {
|
||||
|
||||
@@ -25,17 +25,7 @@ class UserPermissions extends ModelPermissions
|
||||
|
||||
protected function canChangeRole(): bool
|
||||
{
|
||||
// only one role, makes no sense to change it
|
||||
if ($this->user->kirby()->roles()->count() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// users who are not admins cannot change their own role
|
||||
if ($this->user->is($this->model) === true && $this->user->isAdmin() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->model->isLastAdmin() !== true;
|
||||
return $this->model->roles()->count() > 1;
|
||||
}
|
||||
|
||||
protected function canCreate(): bool
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -109,8 +110,9 @@ class Users extends Collection
|
||||
}
|
||||
|
||||
// get role information
|
||||
if (file_exists($root . '/' . $userDirectory . '/index.php') === true) {
|
||||
$credentials = require $root . '/' . $userDirectory . '/index.php';
|
||||
$path = $root . '/' . $userDirectory . '/index.php';
|
||||
if (is_file($path) === true) {
|
||||
$credentials = F::load($path);
|
||||
}
|
||||
|
||||
// create user model based on role
|
||||
|
||||
Reference in New Issue
Block a user