3.4.0
This commit is contained in:
@@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user