Update Composer packages
This commit is contained in:
parent
67c3d8b307
commit
83cb211fe6
219 changed files with 6487 additions and 4444 deletions
|
@ -23,7 +23,7 @@ class Collection
|
|||
protected Api $api;
|
||||
protected $data;
|
||||
protected $model;
|
||||
protected $select;
|
||||
protected $select = null;
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,6 @@ class Collection
|
|||
$this->api = $api;
|
||||
$this->data = $data;
|
||||
$this->model = $schema['model'] ?? null;
|
||||
$this->select = null;
|
||||
$this->view = $schema['view'] ?? null;
|
||||
|
||||
if ($data === null) {
|
||||
|
|
|
@ -91,12 +91,8 @@ class Model
|
|||
*/
|
||||
public function selection(): array
|
||||
{
|
||||
$select = $this->select;
|
||||
|
||||
if ($select === null) {
|
||||
$select = array_keys($this->fields);
|
||||
}
|
||||
|
||||
$select = $this->select;
|
||||
$select ??= array_keys($this->fields);
|
||||
$selection = [];
|
||||
|
||||
foreach ($select as $key => $value) {
|
||||
|
|
|
@ -16,7 +16,7 @@ use TypeError;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Collection extends BaseCollection
|
||||
|
@ -45,21 +45,22 @@ class Collection extends BaseCollection
|
|||
|
||||
/**
|
||||
* Validate the type of every item that is being
|
||||
* added to the collection. They cneed to have
|
||||
* added to the collection. They need to have
|
||||
* the class defined by static::TYPE.
|
||||
*/
|
||||
public function __set(string $key, $value): void
|
||||
{
|
||||
if (
|
||||
is_a($value, static::TYPE) === false
|
||||
) {
|
||||
if (is_a($value, static::TYPE) === false) {
|
||||
throw new TypeError('Each value in the collection must be an instance of ' . static::TYPE);
|
||||
}
|
||||
|
||||
parent::__set($key, $value);
|
||||
}
|
||||
|
||||
public static function factory(array $items)
|
||||
/**
|
||||
* Creates a collection from a nested array structure
|
||||
*/
|
||||
public static function factory(array $items): static
|
||||
{
|
||||
$collection = new static();
|
||||
$className = static::TYPE;
|
||||
|
@ -77,7 +78,11 @@ class Collection extends BaseCollection
|
|||
return $collection;
|
||||
}
|
||||
|
||||
public function render(ModelWithContent $model)
|
||||
/**
|
||||
* Renders each item with a model and returns
|
||||
* an array of all rendered results
|
||||
*/
|
||||
public function render(ModelWithContent $model): array
|
||||
{
|
||||
$props = [];
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ use Kirby\Filesystem\F;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Config
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Kirby\Blueprint;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Extension
|
||||
|
|
|
@ -16,7 +16,7 @@ use ReflectionUnionType;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Factory
|
||||
|
@ -95,7 +95,7 @@ class Factory
|
|||
}
|
||||
|
||||
// union types
|
||||
if (is_a($propType, ReflectionUnionType::class) === true) {
|
||||
if ($propType instanceof ReflectionUnionType) {
|
||||
return static::forUnionType($propType, $value);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Node
|
||||
|
@ -53,7 +53,6 @@ class Node
|
|||
return Factory::make(static::class, $props);
|
||||
}
|
||||
|
||||
|
||||
public static function load(string|array $props): static
|
||||
{
|
||||
// load by path
|
||||
|
|
|
@ -14,7 +14,7 @@ use Kirby\Toolkit\I18n;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeI18n extends NodeProperty
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Kirby\Blueprint;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeIcon extends NodeString
|
||||
|
|
|
@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract class NodeProperty
|
||||
|
|
|
@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeString extends NodeProperty
|
||||
|
|
|
@ -14,7 +14,7 @@ use Kirby\Cms\ModelWithContent;
|
|||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeText extends NodeI18n
|
||||
|
|
|
@ -39,7 +39,8 @@ class Value
|
|||
*
|
||||
* @param int $minutes the number of minutes until the value expires
|
||||
* or an absolute UNIX timestamp
|
||||
* @param int $created the UNIX timestamp when the value has been created
|
||||
* @param int|null $created the UNIX timestamp when the value has been created
|
||||
* (defaults to the current time)
|
||||
*/
|
||||
public function __construct($value, int $minutes = 0, int|null $created = null)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ use Kirby\Http\Router;
|
|||
use Kirby\Http\Uri;
|
||||
use Kirby\Http\Visitor;
|
||||
use Kirby\Session\AutoSession;
|
||||
use Kirby\Template\Snippet;
|
||||
use Kirby\Text\KirbyTag;
|
||||
use Kirby\Text\KirbyTags;
|
||||
use Kirby\Toolkit\A;
|
||||
|
@ -481,28 +482,23 @@ class App
|
|||
|
||||
/**
|
||||
* Try to find a controller by name
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $contentType
|
||||
* @return \Kirby\Toolkit\Controller|null
|
||||
*/
|
||||
protected function controllerLookup(string $name, string $contentType = 'html')
|
||||
protected function controllerLookup(string $name, string $contentType = 'html'): Controller|null
|
||||
{
|
||||
if ($contentType !== null && $contentType !== 'html') {
|
||||
$name .= '.' . $contentType;
|
||||
}
|
||||
|
||||
// controller on disk
|
||||
if ($controller = Controller::load($this->root('controllers') . '/' . $name . '.php')) {
|
||||
// controller from site root
|
||||
$controller = Controller::load($this->root('controllers') . '/' . $name . '.php');
|
||||
// controller from extension
|
||||
$controller ??= $this->extension('controllers', $name);
|
||||
|
||||
if ($controller instanceof Controller) {
|
||||
return $controller;
|
||||
}
|
||||
|
||||
// registry controller
|
||||
if ($controller = $this->extension('controllers', $name)) {
|
||||
if ($controller instanceof Controller) {
|
||||
return $controller;
|
||||
}
|
||||
|
||||
if ($controller !== null) {
|
||||
return new Controller($controller);
|
||||
}
|
||||
|
||||
|
@ -1278,6 +1274,11 @@ class App
|
|||
// set the current locale
|
||||
$this->setCurrentLanguage($language);
|
||||
|
||||
// directly prevent path with incomplete content representation
|
||||
if (Str::endsWith($path, '.') === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// the site is needed a couple times here
|
||||
$site = $this->site();
|
||||
|
||||
|
@ -1557,24 +1558,6 @@ class App
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Environment object
|
||||
* @deprecated 3.7.0 Use `$kirby->environment()` instead
|
||||
*
|
||||
* @return \Kirby\Http\Environment
|
||||
* @deprecated Will be removed in Kirby 3.9.0
|
||||
* @todo Remove in 3.9.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function server()
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
Helpers::deprecated('$kirby->server() has been deprecated and will be removed in Kirby 3.9.0. Use $kirby->environment() instead.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return $this->environment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns the Site object
|
||||
*
|
||||
|
@ -1624,21 +1607,24 @@ class App
|
|||
* Uses the snippet component to create
|
||||
* and return a template snippet
|
||||
*
|
||||
* @param mixed $name
|
||||
* @param array|object $data Variables or an object that becomes `$item`
|
||||
* @param bool $return On `false`, directly echo the snippet
|
||||
* @return string|null
|
||||
* @psalm-return ($return is true ? string : null)
|
||||
*/
|
||||
public function snippet($name, $data = [], bool $return = true): string|null
|
||||
public function snippet(string|array|null $name, $data = [], bool $return = true, bool $slots = false): Snippet|string|null
|
||||
{
|
||||
if (is_object($data) === true) {
|
||||
$data = ['item' => $data];
|
||||
}
|
||||
|
||||
$snippet = ($this->component('snippet'))($this, $name, array_merge($this->data, $data));
|
||||
$snippet = ($this->component('snippet'))(
|
||||
$this,
|
||||
$name,
|
||||
array_merge($this->data, $data),
|
||||
$slots
|
||||
);
|
||||
|
||||
if ($return === true) {
|
||||
if ($return === true || $slots === true) {
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
|
@ -1661,7 +1647,7 @@ class App
|
|||
* and return the Template object
|
||||
*
|
||||
* @internal
|
||||
* @return \Kirby\Cms\Template
|
||||
* @return \Kirby\Template\Template
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $defaultType
|
||||
|
|
|
@ -44,7 +44,7 @@ trait AppCaches
|
|||
|
||||
if (array_key_exists($type, $types) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'app.invalid.cacheType',
|
||||
'key' => 'cache.type.invalid',
|
||||
'data' => ['type' => $type]
|
||||
]);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ trait AppCaches
|
|||
// check if it is a usable cache object
|
||||
if ($cache instanceof Cache === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'app.invalid.cacheType',
|
||||
'key' => 'cache.type.invalid',
|
||||
'data' => ['type' => $type]
|
||||
]);
|
||||
}
|
||||
|
@ -82,9 +82,10 @@ trait AppCaches
|
|||
];
|
||||
}
|
||||
|
||||
$prefix = str_replace(['/', ':'], '_', $this->system()->indexUrl()) .
|
||||
'/' .
|
||||
str_replace('.', '/', $key);
|
||||
$prefix =
|
||||
str_replace(['/', ':'], '_', $this->system()->indexUrl()) .
|
||||
'/' .
|
||||
str_replace('.', '/', $key);
|
||||
|
||||
$defaults = [
|
||||
'active' => true,
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Kirby\Cms;
|
|||
|
||||
use Closure;
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Filesystem\Mime;
|
||||
|
@ -45,6 +46,7 @@ trait AppPlugins
|
|||
// other plugin types
|
||||
'api' => [],
|
||||
'areas' => [],
|
||||
'assetMethods' => [],
|
||||
'authChallenges' => [],
|
||||
'blockMethods' => [],
|
||||
'blockModels' => [],
|
||||
|
@ -147,6 +149,17 @@ trait AppPlugins
|
|||
return $this->extensions['areas'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional asset methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendAssetMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['assetMethods'] = Asset::$methods = array_merge(Asset::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional authentication challenges
|
||||
*
|
||||
|
|
|
@ -60,10 +60,7 @@ trait AppUsers
|
|||
}
|
||||
|
||||
try {
|
||||
// TODO: switch over in 3.9.0 to
|
||||
// return $callback($userAfter);
|
||||
$proxy = new AppUsersImpersonateProxy($this);
|
||||
return $callback->call($proxy, $userAfter);
|
||||
return $callback($userAfter);
|
||||
} catch (Throwable $e) {
|
||||
throw $e;
|
||||
} finally {
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
/**
|
||||
* Temporary proxy class to ease transition
|
||||
* of binding the callback for `$kirby->impersonate()`
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Nico Hoffmann <nico@getkirby.com>,
|
||||
* Lukas Bestle <lukas@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*
|
||||
* @internal
|
||||
* @deprecated Will be removed in Kirby 3.9.0
|
||||
* @todo remove in 3.9.0
|
||||
*/
|
||||
class AppUsersImpersonateProxy
|
||||
{
|
||||
public function __construct(protected App $app)
|
||||
{
|
||||
}
|
||||
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
Helpers::deprecated('Calling $kirby->' . $name . '() as $this->' . $name . '() has been deprecated inside the $kirby->impersonate() callback function. Use a dedicated $kirby object for your call instead of $this. In Kirby 3.9.0 $this will no longer refer to the $kirby object, but the current context of the callback function.');
|
||||
|
||||
return $this->app->$name(...$arguments);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ use Kirby\Http\Idn;
|
|||
use Kirby\Http\Request\Auth\BasicAuth;
|
||||
use Kirby\Session\Session;
|
||||
use Kirby\Toolkit\A;
|
||||
use SensitiveParameter;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
|
@ -381,17 +382,16 @@ class Auth
|
|||
/**
|
||||
* Login a user by email and password
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @param bool $long
|
||||
* @return \Kirby\Cms\User
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occurred with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the email was invalid
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`)
|
||||
*/
|
||||
public function login(string $email, string $password, bool $long = false)
|
||||
{
|
||||
public function login(
|
||||
string $email,
|
||||
#[SensitiveParameter]
|
||||
string $password,
|
||||
bool $long = false
|
||||
): User {
|
||||
// session options
|
||||
$options = [
|
||||
'createMode' => 'cookie',
|
||||
|
@ -412,17 +412,16 @@ class Auth
|
|||
* Login a user by email, password and auth challenge
|
||||
* @since 3.5.0
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @param bool $long
|
||||
* @return \Kirby\Cms\Auth\Status
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occurred with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the email was invalid
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`)
|
||||
*/
|
||||
public function login2fa(string $email, string $password, bool $long = false)
|
||||
{
|
||||
public function login2fa(
|
||||
string $email,
|
||||
#[SensitiveParameter]
|
||||
string $password,
|
||||
bool $long = false
|
||||
): Status {
|
||||
$this->validatePassword($email, $password);
|
||||
return $this->createChallenge($email, $long, '2fa');
|
||||
}
|
||||
|
@ -516,16 +515,15 @@ class Auth
|
|||
* Validates the user credentials and returns the user object on success;
|
||||
* otherwise logs the failed attempt
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @return \Kirby\Cms\User
|
||||
*
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occurred with debug mode off
|
||||
* @throws \Kirby\Exception\NotFoundException If the email was invalid
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`)
|
||||
*/
|
||||
public function validatePassword(string $email, string $password)
|
||||
{
|
||||
public function validatePassword(
|
||||
string $email,
|
||||
#[SensitiveParameter]
|
||||
string $password
|
||||
): User {
|
||||
$email = Idn::decodeEmail($email);
|
||||
|
||||
try {
|
||||
|
@ -798,8 +796,10 @@ class Auth
|
|||
* @throws \Kirby\Exception\InvalidArgumentException If no authentication challenge is active
|
||||
* @throws \Kirby\Exception\LogicException If the authentication challenge is invalid
|
||||
*/
|
||||
public function verifyChallenge(string $code)
|
||||
{
|
||||
public function verifyChallenge(
|
||||
#[SensitiveParameter]
|
||||
string $code
|
||||
) {
|
||||
try {
|
||||
$session = $this->kirby->session();
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Kirby\Cms\Auth;
|
||||
|
||||
use Kirby\Cms\User;
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* Template class for authentication challenges
|
||||
|
@ -48,8 +49,11 @@ abstract class Challenge
|
|||
* @param string $code Code to verify
|
||||
* @return bool
|
||||
*/
|
||||
public static function verify(User $user, string $code): bool
|
||||
{
|
||||
public static function verify(
|
||||
User $user,
|
||||
#[SensitiveParameter]
|
||||
string $code
|
||||
): bool {
|
||||
$hash = $user->kirby()->session()->get('kirby.challenge.code');
|
||||
if (is_string($hash) !== true) {
|
||||
return false;
|
||||
|
|
|
@ -73,6 +73,13 @@ class Block extends Item
|
|||
{
|
||||
parent::__construct($params);
|
||||
|
||||
// @deprecated import old builder format
|
||||
// @todo block.converter remove eventually
|
||||
// @codeCoverageIgnoreStart
|
||||
$params = BlockConverter::builderBlock($params);
|
||||
$params = BlockConverter::editorBlock($params);
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
if (isset($params['type']) === false) {
|
||||
throw new InvalidArgumentException('The block type is missing');
|
||||
}
|
||||
|
|
285
kirby/src/Cms/BlockConverter.php
Normal file
285
kirby/src/Cms/BlockConverter.php
Normal file
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
/**
|
||||
* Converts the data from the old builder and editor fields
|
||||
* to the format supported by the new block field.
|
||||
* @since 3.9.0
|
||||
* @deprecated
|
||||
*
|
||||
* @todo block.converter remove eventually
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class BlockConverter
|
||||
{
|
||||
public static function builderBlock(array $params): array
|
||||
{
|
||||
if (isset($params['_key']) === false) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$params['type'] = $params['_key'];
|
||||
$params['content'] = $params;
|
||||
unset($params['_uid']);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public static function editorBlock(array $params): array
|
||||
{
|
||||
if (static::isEditorBlock($params) === false) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$method = 'editor' . $params['type'];
|
||||
|
||||
if (method_exists(static::class, $method) === true) {
|
||||
$params = static::$method($params);
|
||||
} else {
|
||||
$params = static::editorCustom($params);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public static function editorBlocks(array $blocks = []): array
|
||||
{
|
||||
if (empty($blocks) === true) {
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
if (static::isEditorBlock($blocks[0]) === false) {
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
$list = [];
|
||||
$listStart = null;
|
||||
|
||||
foreach ($blocks as $index => $block) {
|
||||
if (in_array($block['type'], ['ul', 'ol']) === true) {
|
||||
$prev = $blocks[$index-1] ?? null;
|
||||
$next = $blocks[$index+1] ?? null;
|
||||
|
||||
// new list starts here
|
||||
if (!$prev || $prev['type'] !== $block['type']) {
|
||||
$listStart = $index;
|
||||
}
|
||||
|
||||
// add the block to the list
|
||||
$list[] = $block;
|
||||
|
||||
// list ends here
|
||||
if (!$next || $next['type'] !== $block['type']) {
|
||||
$blocks[$listStart] = [
|
||||
'content' => [
|
||||
'text' =>
|
||||
'<' . $block['type'] . '>' .
|
||||
implode(array_map(
|
||||
fn ($item) => '<li>' . $item['content'] . '</li>',
|
||||
$list
|
||||
)) .
|
||||
'</' . $block['type'] . '>',
|
||||
],
|
||||
'type' => 'list'
|
||||
];
|
||||
|
||||
$start = $listStart + 1;
|
||||
$end = $listStart + count($list);
|
||||
|
||||
for ($x = $start; $x <= $end; $x++) {
|
||||
$blocks[$x] = false;
|
||||
}
|
||||
|
||||
$listStart = null;
|
||||
$list = [];
|
||||
}
|
||||
} else {
|
||||
$blocks[$index] = static::editorBlock($block);
|
||||
}
|
||||
}
|
||||
|
||||
return array_filter($blocks);
|
||||
}
|
||||
|
||||
public static function editorBlockquote(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'text' => $params['content']
|
||||
],
|
||||
'type' => 'quote'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorCode(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'language' => $params['attrs']['language'] ?? null,
|
||||
'code' => $params['content']
|
||||
],
|
||||
'type' => 'code'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorCustom(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => array_merge(
|
||||
$params['attrs'] ?? [],
|
||||
[
|
||||
'body' => $params['content'] ?? null
|
||||
]
|
||||
),
|
||||
'type' => $params['type'] ?? 'unknown'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorH1(array $params): array
|
||||
{
|
||||
return static::editorHeading($params, 'h1');
|
||||
}
|
||||
|
||||
public static function editorH2(array $params): array
|
||||
{
|
||||
return static::editorHeading($params, 'h2');
|
||||
}
|
||||
|
||||
public static function editorH3(array $params): array
|
||||
{
|
||||
return static::editorHeading($params, 'h3');
|
||||
}
|
||||
|
||||
public static function editorH4(array $params): array
|
||||
{
|
||||
return static::editorHeading($params, 'h4');
|
||||
}
|
||||
|
||||
public static function editorH5(array $params): array
|
||||
{
|
||||
return static::editorHeading($params, 'h5');
|
||||
}
|
||||
|
||||
public static function editorH6(array $params): array
|
||||
{
|
||||
return static::editorHeading($params, 'h6');
|
||||
}
|
||||
|
||||
public static function editorHr(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [],
|
||||
'type' => 'line'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorHeading(array $params, string $level = 'h1'): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'level' => $level,
|
||||
'text' => $params['content']
|
||||
],
|
||||
'type' => 'heading'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorImage(array $params): array
|
||||
{
|
||||
// internal image
|
||||
if (isset($params['attrs']['id']) === true) {
|
||||
return [
|
||||
'content' => [
|
||||
'alt' => $params['attrs']['alt'] ?? null,
|
||||
'caption' => $params['attrs']['caption'] ?? null,
|
||||
'image' => $params['attrs']['id'] ?? $params['attrs']['src'] ?? null,
|
||||
'location' => 'kirby',
|
||||
'ratio' => $params['attrs']['ratio'] ?? null,
|
||||
],
|
||||
'type' => 'image'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => [
|
||||
'alt' => $params['attrs']['alt'] ?? null,
|
||||
'caption' => $params['attrs']['caption'] ?? null,
|
||||
'src' => $params['attrs']['src'] ?? null,
|
||||
'location' => 'web',
|
||||
'ratio' => $params['attrs']['ratio'] ?? null,
|
||||
],
|
||||
'type' => 'image'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorKirbytext(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'text' => $params['content']
|
||||
],
|
||||
'type' => 'markdown'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorOl(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'text' => $params['content']
|
||||
],
|
||||
'type' => 'list'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorParagraph(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'text' => '<p>' . $params['content'] . '</p>'
|
||||
],
|
||||
'type' => 'text'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorUl(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'text' => $params['content']
|
||||
],
|
||||
'type' => 'list'
|
||||
];
|
||||
}
|
||||
|
||||
public static function editorVideo(array $params): array
|
||||
{
|
||||
return [
|
||||
'content' => [
|
||||
'caption' => $params['attrs']['caption'] ?? null,
|
||||
'url' => $params['attrs']['src'] ?? null
|
||||
],
|
||||
'type' => 'video'
|
||||
];
|
||||
}
|
||||
|
||||
public static function isEditorBlock(array $params): bool
|
||||
{
|
||||
if (isset($params['attrs']) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($params['content'] ?? null) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Data\Json;
|
||||
use Kirby\Data\Yaml;
|
||||
use Kirby\Parsley\Parsley;
|
||||
use Kirby\Parsley\Schema\Blocks as BlockSchema;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
|
@ -58,6 +61,12 @@ class Blocks extends Items
|
|||
{
|
||||
$items = static::extractFromLayouts($items);
|
||||
|
||||
// @deprecated old editor format
|
||||
// @todo block.converter remove eventually
|
||||
// @codeCoverageIgnoreStart
|
||||
$items = BlockConverter::editorBlocks($items);
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return parent::factory($items, $params);
|
||||
}
|
||||
|
||||
|
@ -73,8 +82,13 @@ class Blocks extends Items
|
|||
return [];
|
||||
}
|
||||
|
||||
// no columns = no layout
|
||||
if (array_key_exists('columns', $input[0]) === false) {
|
||||
if (
|
||||
// no columns = no layout
|
||||
array_key_exists('columns', $input[0]) === false ||
|
||||
// @deprecated checks if this is a block for the builder plugin
|
||||
// @todo block.converter remove eventually
|
||||
array_key_exists('_key', $input[0]) === true
|
||||
) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
|
@ -115,8 +129,33 @@ class Blocks extends Items
|
|||
try {
|
||||
$input = Json::decode((string)$input);
|
||||
} catch (Throwable) {
|
||||
$parser = new Parsley((string)$input, new BlockSchema());
|
||||
$input = $parser->blocks();
|
||||
// @deprecated try to import the old YAML format
|
||||
// @todo block.converter remove eventually
|
||||
// @codeCoverageIgnoreStart
|
||||
try {
|
||||
$yaml = Yaml::decode((string)$input);
|
||||
$first = A::first($yaml);
|
||||
|
||||
// check for valid yaml
|
||||
if (
|
||||
empty($yaml) === true ||
|
||||
(
|
||||
isset($first['_key']) === false &&
|
||||
isset($first['type']) === false
|
||||
)
|
||||
) {
|
||||
throw new Exception('Invalid YAML');
|
||||
} else {
|
||||
$input = $yaml;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// the next 2 lines remain after removing block.converter
|
||||
// @codeCoverageIgnoreEnd
|
||||
$parser = new Parsley((string)$input, new BlockSchema());
|
||||
$input = $parser->blocks();
|
||||
// @codeCoverageIgnoreStart
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -115,8 +115,9 @@ class ContentTranslation
|
|||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return empty($this->content) === false ||
|
||||
file_exists($this->contentFile()) === true;
|
||||
return
|
||||
empty($this->content) === false ||
|
||||
file_exists($this->contentFile()) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -132,7 +132,7 @@ class Email
|
|||
*
|
||||
* @param string $name Template name
|
||||
* @param string|null $type `html` or `text`
|
||||
* @return \Kirby\Cms\Template
|
||||
* @return \Kirby\Template\Template
|
||||
*/
|
||||
protected function getTemplate(string $name, string $type = null)
|
||||
{
|
||||
|
|
|
@ -179,7 +179,7 @@ class File extends ModelWithContent
|
|||
public function contentFileData(array $data, string $languageCode = null): array
|
||||
{
|
||||
return A::append($data, [
|
||||
'template' => $this->template(),
|
||||
'template' => $data['template'] ?? $this->template(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -169,11 +169,12 @@ trait FileActions
|
|||
* way of generating files.
|
||||
*
|
||||
* @param array $props
|
||||
* @param bool $move If set to `true`, the source will be deleted
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
* @throws \Kirby\Exception\LogicException
|
||||
*/
|
||||
public static function create(array $props)
|
||||
public static function create(array $props, bool $move = false)
|
||||
{
|
||||
if (isset($props['source'], $props['parent']) === false) {
|
||||
throw new InvalidArgumentException('Please provide the "source" and "parent" props for the File');
|
||||
|
@ -204,12 +205,16 @@ trait FileActions
|
|||
$file = $file->clone(['content' => $form->strings(true)]);
|
||||
|
||||
// run the hook
|
||||
return $file->commit('create', compact('file', 'upload'), function ($file, $upload) {
|
||||
$arguments = compact('file', 'upload');
|
||||
return $file->commit('create', $arguments, function ($file, $upload) use ($move) {
|
||||
// remove all public versions, lock and clear UUID cache
|
||||
$file->unpublish();
|
||||
|
||||
// only move the original source if intended
|
||||
$method = $move === true ? 'move' : 'copy';
|
||||
|
||||
// overwrite the original
|
||||
if (F::copy($upload->root(), $file->root(), true) !== true) {
|
||||
if (F::$method($upload->root(), $file->root(), true) !== true) {
|
||||
throw new LogicException('The file could not be created');
|
||||
}
|
||||
|
||||
|
@ -280,10 +285,11 @@ trait FileActions
|
|||
* source.
|
||||
*
|
||||
* @param string $source
|
||||
* @param bool $move If set to `true`, the source will be deleted
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\LogicException
|
||||
*/
|
||||
public function replace(string $source)
|
||||
public function replace(string $source, bool $move = false)
|
||||
{
|
||||
$file = $this->clone();
|
||||
|
||||
|
@ -292,12 +298,15 @@ trait FileActions
|
|||
'upload' => $file->asset($source)
|
||||
];
|
||||
|
||||
return $this->commit('replace', $arguments, function ($file, $upload) {
|
||||
return $this->commit('replace', $arguments, function ($file, $upload) use ($move) {
|
||||
// delete all public versions
|
||||
$file->unpublish(true);
|
||||
|
||||
// only move the original source if intended
|
||||
$method = $move === true ? 'move' : 'copy';
|
||||
|
||||
// overwrite the original
|
||||
if (F::copy($upload->root(), $file->root(), true) !== true) {
|
||||
if (F::$method($upload->root(), $file->root(), true) !== true) {
|
||||
throw new LogicException('The file could not be created');
|
||||
}
|
||||
|
||||
|
|
|
@ -57,16 +57,17 @@ trait HasFiles
|
|||
* Creates a new file
|
||||
*
|
||||
* @param array $props
|
||||
* @param bool $move If set to `true`, the source will be deleted
|
||||
* @return \Kirby\Cms\File
|
||||
*/
|
||||
public function createFile(array $props)
|
||||
public function createFile(array $props, bool $move = false)
|
||||
{
|
||||
$props = array_merge($props, [
|
||||
'parent' => $this,
|
||||
'url' => null
|
||||
]);
|
||||
|
||||
return File::create($props);
|
||||
return File::create($props, $move);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,18 +19,57 @@ use Kirby\Toolkit\Str;
|
|||
class Helpers
|
||||
{
|
||||
/**
|
||||
* Triggers a deprecation warning if debug mode is active
|
||||
* Allows to disable specific deprecation warnings
|
||||
* by setting them to `false`.
|
||||
* You can do this by putting the following code in
|
||||
* `site/config/config.php`:
|
||||
*
|
||||
* @param string $message
|
||||
* ```php
|
||||
* Helpers::$deprecations['<deprecation-key>'] = false;
|
||||
* ```
|
||||
*/
|
||||
public static $deprecations = [
|
||||
// Passing the $slot or $slots variables to snippets is
|
||||
// deprecated and will break in a future version.
|
||||
'snippet-pass-slots' => true,
|
||||
|
||||
// The `Toolkit\Query` class has been deprecated and will
|
||||
// be removed in a future version. Use `Query\Query` instead:
|
||||
// Kirby\Query\Query::factory($query)->resolve($data).
|
||||
'toolkit-query-class' => true,
|
||||
|
||||
// Passing an empty string as value to `Xml::attr()` has been
|
||||
// deprecated. In a future version, passing an empty string won't
|
||||
// omit the attribute anymore but render it with an empty value.
|
||||
// To omit the attribute, please pass `null`.
|
||||
'xml-attr-empty-string' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Triggers a deprecation warning if debug mode is active
|
||||
* and warning has not been surpressed via `Helpers::$deprecations`
|
||||
*
|
||||
* @param string|null $key If given, the key will be checked against the static array
|
||||
* @return bool Whether the warning was triggered
|
||||
*/
|
||||
public static function deprecated(string $message): bool
|
||||
public static function deprecated(string $message, string|null $key = null): bool
|
||||
{
|
||||
if (App::instance()->option('debug') === true) {
|
||||
return trigger_error($message, E_USER_DEPRECATED) === true;
|
||||
// only trigger warning in debug mode or when running PHPUnit tests
|
||||
// @codeCoverageIgnoreStart
|
||||
if (
|
||||
App::instance()->option('debug') !== true &&
|
||||
(defined('KIRBY_TESTING') !== true || KIRBY_TESTING !== true)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// don't trigger the warning if disabled by default or by the dev
|
||||
if ($key !== null && (static::$deprecations[$key] ?? true) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return trigger_error($message, E_USER_DEPRECATED) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,7 +45,8 @@ class Html extends \Kirby\Toolkit\Html
|
|||
}
|
||||
}
|
||||
|
||||
// only valid value for 'rel' is 'alternate stylesheet', if 'title' is given as well
|
||||
// only valid value for 'rel' is 'alternate stylesheet',
|
||||
// if 'title' is given as well
|
||||
if (
|
||||
($options['rel'] ?? '') !== 'alternate stylesheet' ||
|
||||
($options['title'] ?? '') === ''
|
||||
|
|
|
@ -26,6 +26,8 @@ class Item
|
|||
|
||||
public const ITEMS_CLASS = Items::class;
|
||||
|
||||
protected Field|null $field;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -57,6 +59,7 @@ class Item
|
|||
|
||||
$this->id = $params['id'] ?? Str::uuid();
|
||||
$this->params = $params;
|
||||
$this->field = $params['field'] ?? null;
|
||||
$this->parent = $params['parent'] ?? App::instance()->site();
|
||||
$this->siblings = $params['siblings'] ?? new $siblingsClass();
|
||||
}
|
||||
|
@ -72,6 +75,14 @@ class Item
|
|||
return new static($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent field if known
|
||||
*/
|
||||
public function field(): Field|null
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique item id (UUID v4)
|
||||
*
|
||||
|
|
|
@ -19,6 +19,8 @@ class Items extends Collection
|
|||
{
|
||||
public const ITEM_CLASS = Item::class;
|
||||
|
||||
protected Field|null $field;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
|
@ -39,6 +41,7 @@ class Items extends Collection
|
|||
{
|
||||
$this->options = $options;
|
||||
$this->parent = $options['parent'] ?? App::instance()->site();
|
||||
$this->field = $options['field'] ?? null;
|
||||
|
||||
parent::__construct($objects, $this->parent);
|
||||
}
|
||||
|
@ -54,6 +57,7 @@ class Items extends Collection
|
|||
public static function factory(array $items = null, array $params = [])
|
||||
{
|
||||
$options = array_merge([
|
||||
'field' => null,
|
||||
'options' => [],
|
||||
'parent' => App::instance()->site(),
|
||||
], $params);
|
||||
|
@ -74,6 +78,7 @@ class Items extends Collection
|
|||
continue;
|
||||
}
|
||||
|
||||
$params['field'] = $options['field'];
|
||||
$params['options'] = $options['options'];
|
||||
$params['parent'] = $options['parent'];
|
||||
$params['siblings'] = $collection;
|
||||
|
@ -85,6 +90,14 @@ class Items extends Collection
|
|||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent field if known
|
||||
*/
|
||||
public function field(): Field|null
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the items to an array
|
||||
*
|
||||
|
|
|
@ -658,12 +658,8 @@ class Language extends Model
|
|||
*/
|
||||
public function url(): string
|
||||
{
|
||||
$url = $this->url;
|
||||
|
||||
if ($url === null) {
|
||||
$url = '/' . $this->code;
|
||||
}
|
||||
|
||||
$url = $this->url;
|
||||
$url ??= '/' . $this->code;
|
||||
return Url::makeAbsolute($url, $this->kirby()->url());
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ class Layout extends Item
|
|||
parent::__construct($params);
|
||||
|
||||
$this->columns = LayoutColumns::factory($params['columns'] ?? [], [
|
||||
'field' => $this->field,
|
||||
'parent' => $this->parent
|
||||
]);
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ class LayoutColumn extends Item
|
|||
parent::__construct($params);
|
||||
|
||||
$this->blocks = Blocks::factory($params['blocks'] ?? [], [
|
||||
'field' => $this->field,
|
||||
'parent' => $this->parent
|
||||
]);
|
||||
|
||||
|
|
|
@ -98,6 +98,9 @@ class Layouts extends Items
|
|||
}
|
||||
}
|
||||
|
||||
return Blocks::factory($blocks);
|
||||
return Blocks::factory($blocks, [
|
||||
'field' => $this->field,
|
||||
'parent' => $this->parent
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -629,13 +629,11 @@ abstract class ModelWithContent extends Model implements Identifiable
|
|||
]);
|
||||
|
||||
// validate the input
|
||||
if ($validate === true) {
|
||||
if ($form->isInvalid() === true) {
|
||||
throw new InvalidArgumentException([
|
||||
'fallback' => 'Invalid form with errors',
|
||||
'details' => $form->errors()
|
||||
]);
|
||||
}
|
||||
if ($validate === true && $form->isInvalid() === true) {
|
||||
throw new InvalidArgumentException([
|
||||
'fallback' => 'Invalid form with errors',
|
||||
'details' => $form->errors()
|
||||
]);
|
||||
}
|
||||
|
||||
$arguments = [static::CLASS_ALIAS => $this, 'values' => $form->data(), 'strings' => $form->strings(), 'languageCode' => $languageCode];
|
||||
|
|
|
@ -96,7 +96,7 @@ class Page extends ModelWithContent
|
|||
* The template, that should be loaded
|
||||
* if it exists
|
||||
*
|
||||
* @var \Kirby\Cms\Template
|
||||
* @var \Kirby\Template\Template
|
||||
*/
|
||||
protected $intendedTemplate;
|
||||
|
||||
|
@ -143,7 +143,7 @@ class Page extends ModelWithContent
|
|||
/**
|
||||
* The intended page template
|
||||
*
|
||||
* @var \Kirby\Cms\Template
|
||||
* @var \Kirby\Template\Template
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
|
@ -504,7 +504,7 @@ class Page extends ModelWithContent
|
|||
* Returns the template that should be
|
||||
* loaded if it exists.
|
||||
*
|
||||
* @return \Kirby\Cms\Template
|
||||
* @return \Kirby\Template\Template
|
||||
*/
|
||||
public function intendedTemplate()
|
||||
{
|
||||
|
@ -1096,7 +1096,7 @@ class Page extends ModelWithContent
|
|||
/**
|
||||
* @internal
|
||||
* @param mixed $type
|
||||
* @return \Kirby\Cms\Template
|
||||
* @return \Kirby\Template\Template
|
||||
* @throws \Kirby\Exception\NotFoundException If the content representation cannot be found
|
||||
*/
|
||||
public function representation($type)
|
||||
|
@ -1277,13 +1277,13 @@ class Page extends ModelWithContent
|
|||
public function slug(string $languageCode = null): string
|
||||
{
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
if ($languageCode === null) {
|
||||
$languageCode = $this->kirby()->languageCode();
|
||||
}
|
||||
|
||||
$languageCode ??= $this->kirby()->languageCode();
|
||||
$defaultLanguageCode = $this->kirby()->defaultLanguage()->code();
|
||||
|
||||
if ($languageCode !== $defaultLanguageCode && $translation = $this->translations()->find($languageCode)) {
|
||||
if (
|
||||
$languageCode !== $defaultLanguageCode &&
|
||||
$translation = $this->translations()->find($languageCode)
|
||||
) {
|
||||
return $translation->slug() ?? $this->slug;
|
||||
}
|
||||
}
|
||||
|
@ -1313,7 +1313,7 @@ class Page extends ModelWithContent
|
|||
/**
|
||||
* Returns the final template
|
||||
*
|
||||
* @return \Kirby\Cms\Template
|
||||
* @return \Kirby\Template\Template
|
||||
*/
|
||||
public function template()
|
||||
{
|
||||
|
|
|
@ -223,7 +223,7 @@ trait PageActions
|
|||
throw new InvalidArgumentException('Use the changeSlug method to change the slug for the default language');
|
||||
}
|
||||
|
||||
$arguments = ['page' => $this, 'slug' => $slug, 'languageCode' => $languageCode];
|
||||
$arguments = ['page' => $this, 'slug' => $slug, 'languageCode' => $language->code()];
|
||||
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()) {
|
||||
|
@ -521,9 +521,9 @@ trait PageActions
|
|||
public static function create(array $props)
|
||||
{
|
||||
// clean up the slug
|
||||
$props['slug'] = Str::slug($props['slug'] ?? $props['content']['title'] ?? null);
|
||||
$props['template'] = $props['model'] = strtolower($props['template'] ?? 'default');
|
||||
$props['isDraft'] = ($props['draft'] ?? true);
|
||||
$props['slug'] = Str::slug($props['slug'] ?? $props['content']['title'] ?? null);
|
||||
$props['template'] = $props['model'] = strtolower($props['template'] ?? 'default');
|
||||
$props['isDraft'] ??= $props['draft'] ?? true;
|
||||
|
||||
// make sure that a UUID gets generated and
|
||||
// added to content right away
|
||||
|
@ -620,9 +620,7 @@ trait PageActions
|
|||
->count();
|
||||
|
||||
// default positioning at the end
|
||||
if ($num === null) {
|
||||
$num = $max;
|
||||
}
|
||||
$num ??= $max;
|
||||
|
||||
// avoid zeros or negative numbers
|
||||
if ($num < 1) {
|
||||
|
|
|
@ -204,7 +204,12 @@ class PageRules
|
|||
]);
|
||||
}
|
||||
|
||||
if (count($page->blueprints()) <= 1) {
|
||||
$blueprints = $page->blueprints();
|
||||
|
||||
if (
|
||||
count($blueprints) <= 1 ||
|
||||
in_array($template, array_column($blueprints, 'name')) === false
|
||||
) {
|
||||
throw new LogicException([
|
||||
'key' => 'page.changeTemplate.invalid',
|
||||
'data' => ['slug' => $page->slug()]
|
||||
|
|
|
@ -154,10 +154,10 @@ class Pages extends Collection
|
|||
*
|
||||
* @param array $pages
|
||||
* @param \Kirby\Cms\Model|null $model
|
||||
* @param bool $draft
|
||||
* @param bool|null $draft
|
||||
* @return static
|
||||
*/
|
||||
public static function factory(array $pages, Model $model = null, bool $draft = false)
|
||||
public static function factory(array $pages, Model $model = null, bool $draft = null)
|
||||
{
|
||||
$model ??= App::instance()->site();
|
||||
$children = new static([], $model);
|
||||
|
@ -175,7 +175,7 @@ class Pages extends Collection
|
|||
$props['kirby'] = $kirby;
|
||||
$props['parent'] = $parent;
|
||||
$props['site'] = $site;
|
||||
$props['isDraft'] = $draft;
|
||||
$props['isDraft'] = $draft ?? $props['isDraft'] ?? $props['draft'] ?? false;
|
||||
|
||||
$page = Page::factory($props);
|
||||
|
||||
|
|
|
@ -255,9 +255,7 @@ class Plugin extends Model
|
|||
}
|
||||
}
|
||||
|
||||
if ($option === null) {
|
||||
$option = $kirby->option('updates') ?? true;
|
||||
}
|
||||
$option ??= $kirby->option('updates') ?? true;
|
||||
|
||||
if ($option !== true) {
|
||||
return null;
|
||||
|
|
|
@ -31,63 +31,37 @@ use Throwable;
|
|||
*/
|
||||
class System
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\App
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
// cache
|
||||
protected UpdateStatus|null $updateStatus = null;
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\App $app
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
public function __construct(protected App $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
// try to create all folders that could be missing
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a writable accounts folder
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function accounts(): bool
|
||||
{
|
||||
return is_writable($this->app->root('accounts'));
|
||||
return is_writable($this->app->root('accounts')) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a writable content folder
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function content(): bool
|
||||
{
|
||||
return is_writable($this->app->root('content'));
|
||||
return is_writable($this->app->root('content')) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for an existing curl extension
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function curl(): bool
|
||||
{
|
||||
return extension_loaded('curl');
|
||||
return extension_loaded('curl') === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,7 +70,6 @@ class System
|
|||
* root. Otherwise it will return null.
|
||||
*
|
||||
* @param string $folder 'git', 'content', 'site', 'kirby'
|
||||
* @return string|null
|
||||
*/
|
||||
public function exposedFileUrl(string $folder): string|null
|
||||
{
|
||||
|
@ -142,19 +115,20 @@ class System
|
|||
* root. Otherwise it will return null.
|
||||
*
|
||||
* @param string $folder 'git', 'content', 'site', 'kirby'
|
||||
* @return string|null
|
||||
*/
|
||||
public function folderUrl(string $folder): string|null
|
||||
{
|
||||
$index = $this->app->root('index');
|
||||
$root = match ($folder) {
|
||||
'git' => $index . '/.git',
|
||||
default => $this->app->root($folder)
|
||||
};
|
||||
|
||||
if ($folder === 'git') {
|
||||
$root = $index . '/.git';
|
||||
} else {
|
||||
$root = $this->app->root($folder);
|
||||
}
|
||||
|
||||
if ($root === null || is_dir($root) === false || is_dir($index) === false) {
|
||||
if (
|
||||
$root === null ||
|
||||
is_dir($root) === false ||
|
||||
is_dir($index) === false
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -180,22 +154,22 @@ class System
|
|||
/**
|
||||
* Returns the app's human-readable
|
||||
* index URL without scheme
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function indexUrl(): string
|
||||
{
|
||||
return $this->app->url('index', true)->setScheme(null)->setSlash(false)->toString();
|
||||
return $this->app->url('index', true)
|
||||
->setScheme(null)
|
||||
->setSlash(false)
|
||||
->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the most important folders
|
||||
* if they don't exist yet
|
||||
*
|
||||
* @return void
|
||||
* @throws \Kirby\Exception\PermissionException
|
||||
*/
|
||||
public function init()
|
||||
public function init(): void
|
||||
{
|
||||
// init /site/accounts
|
||||
try {
|
||||
|
@ -231,18 +205,16 @@ class System
|
|||
* On a public server the panel.install
|
||||
* option must be explicitly set to true
|
||||
* to get the installer up and running.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInstallable(): bool
|
||||
{
|
||||
return $this->isLocal() === true || $this->app->option('panel.install', false) === true;
|
||||
return
|
||||
$this->isLocal() === true ||
|
||||
$this->app->option('panel.install', false) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Kirby is already installed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInstalled(): bool
|
||||
{
|
||||
|
@ -251,8 +223,6 @@ class System
|
|||
|
||||
/**
|
||||
* Check if this is a local installation
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLocal(): bool
|
||||
{
|
||||
|
@ -261,8 +231,6 @@ class System
|
|||
|
||||
/**
|
||||
* Check if all tests pass
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOk(): bool
|
||||
{
|
||||
|
@ -311,7 +279,9 @@ class System
|
|||
$pubKey = F::read($this->app->root('kirby') . '/kirby.pub');
|
||||
|
||||
// verify the license signature
|
||||
if (openssl_verify(json_encode($data), hex2bin($license['signature']), $pubKey, 'RSA-SHA256') !== 1) {
|
||||
$data = json_encode($data);
|
||||
$signature = hex2bin($license['signature']);
|
||||
if (openssl_verify($data, $signature, $pubKey, 'RSA-SHA256') !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -338,9 +308,7 @@ class System
|
|||
*/
|
||||
protected function licenseUrl(string $url = null): string
|
||||
{
|
||||
if ($url === null) {
|
||||
$url = $this->indexUrl();
|
||||
}
|
||||
$url ??= $this->indexUrl();
|
||||
|
||||
// remove common "testing" subdomains as well as www.
|
||||
// to ensure that installations of the same site have
|
||||
|
@ -371,8 +339,6 @@ class System
|
|||
* Returns the configured UI modes for the login form
|
||||
* with their respective options
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the configuration is invalid
|
||||
* (only in debug mode)
|
||||
*/
|
||||
|
@ -431,45 +397,38 @@ class System
|
|||
|
||||
/**
|
||||
* Check for an existing mbstring extension
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function mbString(): bool
|
||||
{
|
||||
return extension_loaded('mbstring');
|
||||
return extension_loaded('mbstring') === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a writable media folder
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function media(): bool
|
||||
{
|
||||
return is_writable($this->app->root('media'));
|
||||
return is_writable($this->app->root('media')) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a valid PHP version
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function php(): bool
|
||||
{
|
||||
return
|
||||
version_compare(PHP_VERSION, '8.0.0', '>=') === true &&
|
||||
version_compare(PHP_VERSION, '8.2.0', '<') === true;
|
||||
version_compare(PHP_VERSION, '8.3.0', '<') === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted collection of all
|
||||
* installed plugins
|
||||
*
|
||||
* @return \Kirby\Cms\Collection
|
||||
*/
|
||||
public function plugins()
|
||||
public function plugins(): Collection
|
||||
{
|
||||
return (new Collection(App::instance()->plugins()))->sortBy('name', 'asc');
|
||||
$plugins = new Collection($this->app->plugins());
|
||||
return $plugins->sortBy('name', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -477,24 +436,17 @@ class System
|
|||
* and adds it to the .license file in the config
|
||||
* folder if possible.
|
||||
*
|
||||
* @param string|null $license
|
||||
* @param string|null $email
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\Exception
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function register(string $license = null, string $email = null): bool
|
||||
{
|
||||
if (Str::startsWith($license, 'K3-PRO-') === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'license.format'
|
||||
]);
|
||||
throw new InvalidArgumentException(['key' => 'license.format']);
|
||||
}
|
||||
|
||||
if (V::email($email) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'license.email'
|
||||
]);
|
||||
throw new InvalidArgumentException(['key' => 'license.email']);
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
@ -534,8 +486,6 @@ class System
|
|||
|
||||
/**
|
||||
* Check for a valid server environment
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function server(): bool
|
||||
{
|
||||
|
@ -544,8 +494,6 @@ class System
|
|||
|
||||
/**
|
||||
* Returns the detected server software
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function serverSoftware(): string|null
|
||||
{
|
||||
|
@ -566,18 +514,14 @@ class System
|
|||
|
||||
/**
|
||||
* Check for a writable sessions folder
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sessions(): bool
|
||||
{
|
||||
return is_writable($this->app->root('sessions'));
|
||||
return is_writable($this->app->root('sessions')) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an status array of all checks
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function status(): array
|
||||
{
|
||||
|
@ -597,23 +541,18 @@ class System
|
|||
* Returns the site's title as defined in the
|
||||
* content file or `site.yml` blueprint
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
$site = $this->app->site();
|
||||
|
||||
if ($site->title()->isNotEmpty()) {
|
||||
if ($site->title()->isNotEmpty() === true) {
|
||||
return $site->title()->value();
|
||||
}
|
||||
|
||||
return $site->blueprint()->title();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->status();
|
||||
|
@ -633,7 +572,9 @@ class System
|
|||
}
|
||||
|
||||
$kirby = $this->app;
|
||||
$option = $kirby->option('updates.kirby') ?? $kirby->option('updates') ?? true;
|
||||
$option =
|
||||
$kirby->option('updates.kirby') ??
|
||||
$kirby->option('updates', true);
|
||||
|
||||
if ($option === false) {
|
||||
return null;
|
||||
|
@ -648,11 +589,8 @@ class System
|
|||
|
||||
/**
|
||||
* Upgrade to the new folder separator
|
||||
*
|
||||
* @param string $root
|
||||
* @return void
|
||||
*/
|
||||
public static function upgradeContent(string $root)
|
||||
public static function upgradeContent(string $root): void
|
||||
{
|
||||
$index = Dir::read($root);
|
||||
|
||||
|
@ -666,4 +604,12 @@ class System
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -435,9 +435,7 @@ class UpdateStatus
|
|||
// verify that we found at least one possible version;
|
||||
// otherwise try the `$maxVersion` as a last chance before
|
||||
// concluding at the top that we cannot solve the task
|
||||
if ($incidentVersion === null) {
|
||||
$incidentVersion = $maxVersion;
|
||||
}
|
||||
$incidentVersion ??= $maxVersion;
|
||||
|
||||
// we need a version that fixes all vulnerabilities, so use the
|
||||
// "largest of the smallest" fixed versions
|
||||
|
|
|
@ -10,6 +10,7 @@ use Kirby\Filesystem\F;
|
|||
use Kirby\Panel\User as Panel;
|
||||
use Kirby\Session\Session;
|
||||
use Kirby\Toolkit\Str;
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* The `$user` object represents a
|
||||
|
@ -274,11 +275,11 @@ class User extends ModelWithContent
|
|||
* which will leave it as `null`
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $password
|
||||
* @return string|null
|
||||
*/
|
||||
public static function hashPassword($password): string|null
|
||||
{
|
||||
public static function hashPassword(
|
||||
#[SensitiveParameter]
|
||||
string $password = null
|
||||
): string|null {
|
||||
if ($password !== null) {
|
||||
$password = password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
@ -372,8 +373,9 @@ class User extends ModelWithContent
|
|||
*/
|
||||
public function isLastAdmin(): bool
|
||||
{
|
||||
return $this->role()->isAdmin() === true &&
|
||||
$this->kirby()->users()->filter('role', 'admin')->count() <= 1;
|
||||
return
|
||||
$this->role()->isAdmin() === true &&
|
||||
$this->kirby()->users()->filter('role', 'admin')->count() <= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -410,12 +412,13 @@ class User extends ModelWithContent
|
|||
/**
|
||||
* Logs the user in
|
||||
*
|
||||
* @param string $password
|
||||
* @param \Kirby\Session\Session|array|null $session Session options or session object to set the user in
|
||||
* @return bool
|
||||
*/
|
||||
public function login(string $password, $session = null): bool
|
||||
{
|
||||
public function login(
|
||||
#[SensitiveParameter]
|
||||
string $password,
|
||||
$session = null
|
||||
): bool {
|
||||
$this->validatePassword($password);
|
||||
$this->loginPasswordless($session);
|
||||
|
||||
|
@ -750,11 +753,12 @@ class User extends ModelWithContent
|
|||
/**
|
||||
* Sets the user's password hash
|
||||
*
|
||||
* @param string $password|null
|
||||
* @return $this
|
||||
*/
|
||||
protected function setPassword(string $password = null)
|
||||
{
|
||||
protected function setPassword(
|
||||
#[SensitiveParameter]
|
||||
string $password = null
|
||||
): static {
|
||||
$this->password = $password;
|
||||
return $this;
|
||||
}
|
||||
|
@ -848,15 +852,14 @@ class User extends ModelWithContent
|
|||
/**
|
||||
* Compares the given password with the stored one
|
||||
*
|
||||
* @param string $password|null
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Kirby\Exception\NotFoundException If the user has no password
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the entered password is not valid
|
||||
* or does not match the user password
|
||||
*/
|
||||
public function validatePassword(string $password = null): bool
|
||||
{
|
||||
public function validatePassword(
|
||||
#[SensitiveParameter]
|
||||
string $password = null
|
||||
): bool {
|
||||
if (empty($this->password()) === true) {
|
||||
throw new NotFoundException(['key' => 'user.password.undefined']);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ use Kirby\Filesystem\F;
|
|||
use Kirby\Form\Form;
|
||||
use Kirby\Http\Idn;
|
||||
use Kirby\Toolkit\Str;
|
||||
use SensitiveParameter;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
|
@ -102,12 +103,11 @@ trait UserActions
|
|||
|
||||
/**
|
||||
* Changes the user password
|
||||
*
|
||||
* @param string $password
|
||||
* @return static
|
||||
*/
|
||||
public function changePassword(string $password)
|
||||
{
|
||||
public function changePassword(
|
||||
#[SensitiveParameter]
|
||||
string $password
|
||||
): static {
|
||||
return $this->commit('changePassword', ['user' => $this, 'password' => $password], function ($user, $password) {
|
||||
$user = $user->clone([
|
||||
'password' => $password = User::hashPassword($password)
|
||||
|
@ -379,12 +379,11 @@ trait UserActions
|
|||
|
||||
/**
|
||||
* Writes the password to disk
|
||||
*
|
||||
* @param string|null $password
|
||||
* @return bool
|
||||
*/
|
||||
protected function writePassword(string $password = null): bool
|
||||
{
|
||||
protected function writePassword(
|
||||
#[SensitiveParameter]
|
||||
string $password = null
|
||||
): bool {
|
||||
return F::write($this->root() . '/.htpasswd', $password);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use Kirby\Exception\LogicException;
|
|||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* Validators for all user actions
|
||||
|
@ -83,13 +84,13 @@ class UserRules
|
|||
/**
|
||||
* Validates if the password can be changed
|
||||
*
|
||||
* @param \Kirby\Cms\User $user
|
||||
* @param string $password
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the password
|
||||
*/
|
||||
public static function changePassword(User $user, string $password): bool
|
||||
{
|
||||
public static function changePassword(
|
||||
User $user,
|
||||
#[SensitiveParameter]
|
||||
string $password
|
||||
): bool {
|
||||
if ($user->permissions()->changePassword() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'user.changePassword.permission',
|
||||
|
@ -193,12 +194,13 @@ class UserRules
|
|||
}
|
||||
|
||||
// check user permissions (if not on install)
|
||||
if ($user->kirby()->users()->count() > 0) {
|
||||
if ($user->permissions()->create() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'user.create.permission'
|
||||
]);
|
||||
}
|
||||
if (
|
||||
$user->kirby()->users()->count() > 0 &&
|
||||
$user->permissions()->create() !== true
|
||||
) {
|
||||
throw new PermissionException([
|
||||
'key' => 'user.create.permission'
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -332,13 +334,13 @@ class UserRules
|
|||
/**
|
||||
* Validates a password
|
||||
*
|
||||
* @param \Kirby\Cms\User $user
|
||||
* @param string $password
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the password is too short
|
||||
*/
|
||||
public static function validPassword(User $user, string $password): bool
|
||||
{
|
||||
public static function validPassword(
|
||||
User $user,
|
||||
#[SensitiveParameter]
|
||||
string $password
|
||||
): bool {
|
||||
if (Str::length($password ?? null) < 8) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'user.password.invalid',
|
||||
|
|
|
@ -55,8 +55,9 @@ class Data
|
|||
|
||||
// find a handler or alias
|
||||
$alias = static::$aliases[$type] ?? null;
|
||||
$handler = static::$handlers[$type] ??
|
||||
($alias ? static::$handlers[$alias] ?? null : null);
|
||||
$handler =
|
||||
static::$handlers[$type] ??
|
||||
($alias ? static::$handlers[$alias] ?? null : null);
|
||||
|
||||
if ($handler === null || class_exists($handler) === false) {
|
||||
throw new Exception('Missing handler for type: "' . $type . '"');
|
||||
|
|
|
@ -6,6 +6,7 @@ use Kirby\Blueprint\Node;
|
|||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Option\Options;
|
||||
use Kirby\Option\OptionsApi;
|
||||
use Kirby\Option\OptionsProvider;
|
||||
use Kirby\Option\OptionsQuery;
|
||||
|
||||
/**
|
||||
|
@ -20,7 +21,18 @@ use Kirby\Option\OptionsQuery;
|
|||
class FieldOptions extends Node
|
||||
{
|
||||
public function __construct(
|
||||
public Options|OptionsApi|OptionsQuery|null $options = null
|
||||
/**
|
||||
* The option source, either a fixed collection or
|
||||
* a dynamic provider
|
||||
*/
|
||||
public Options|OptionsProvider|null $options = null,
|
||||
|
||||
/**
|
||||
* Whether to escape special HTML characters in
|
||||
* the option text for safe output in the Panel;
|
||||
* only set to `false` if the text is later escaped!
|
||||
*/
|
||||
public bool $safeMode = true
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -31,7 +43,7 @@ class FieldOptions extends Node
|
|||
return parent::defaults();
|
||||
}
|
||||
|
||||
public static function factory(array $props): static
|
||||
public static function factory(array $props, bool $safeMode = true): static
|
||||
{
|
||||
$options = match ($props['type']) {
|
||||
'api' => OptionsApi::factory($props),
|
||||
|
@ -39,20 +51,23 @@ class FieldOptions extends Node
|
|||
default => Options::factory($props['options'] ?? [])
|
||||
};
|
||||
|
||||
return new static($options);
|
||||
return new static($options, $safeMode);
|
||||
}
|
||||
|
||||
public static function polyfill(array $props = []): array
|
||||
{
|
||||
if (is_string($props['options'] ?? null) === true) {
|
||||
$props['options'] = match ($props['options']) {
|
||||
'api' => ['type' => 'api'] +
|
||||
OptionsApi::polyfill($props['api'] ?? null),
|
||||
'api' =>
|
||||
['type' => 'api'] +
|
||||
OptionsApi::polyfill($props['api'] ?? null),
|
||||
|
||||
'query' => ['type' => 'query'] +
|
||||
OptionsQuery::polyfill($props['query'] ?? null),
|
||||
'query' =>
|
||||
['type' => 'query'] +
|
||||
OptionsQuery::polyfill($props['query'] ?? null),
|
||||
|
||||
default => [ 'type' => 'query', 'query' => $props['options']]
|
||||
default =>
|
||||
[ 'type' => 'query', 'query' => $props['options']]
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -82,8 +97,8 @@ class FieldOptions extends Node
|
|||
return $this->options;
|
||||
}
|
||||
|
||||
// resolve OptionsApi or OptionsQuery to Options
|
||||
return $this->options = $this->options->resolve($model);
|
||||
// resolve OptionsProvider (OptionsApi or OptionsQuery) to Options
|
||||
return $this->options = $this->options->resolve($model, $this->safeMode);
|
||||
}
|
||||
|
||||
public function render(ModelWithContent $model): array
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace Kirby\Filesystem;
|
||||
|
||||
use Kirby\Cms\FileModifications;
|
||||
use Kirby\Cms\HasMethods;
|
||||
use Kirby\Exception\BadMethodCallException;
|
||||
|
||||
/**
|
||||
* Anything in your public path can be converted
|
||||
|
@ -20,6 +22,7 @@ class Asset
|
|||
{
|
||||
use IsFile;
|
||||
use FileModifications;
|
||||
use HasMethods;
|
||||
|
||||
/**
|
||||
* Relative file path
|
||||
|
@ -38,6 +41,31 @@ class Asset
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic caller for asset methods
|
||||
*
|
||||
* @throws \Kirby\Exception\BadMethodCallException
|
||||
*/
|
||||
public function __call(string $method, array $arguments = [])
|
||||
{
|
||||
// public property access
|
||||
if (isset($this->$method) === true) {
|
||||
return $this->$method;
|
||||
}
|
||||
|
||||
// asset method proxy
|
||||
if (method_exists($this->asset(), $method)) {
|
||||
return $this->asset()->$method(...$arguments);
|
||||
}
|
||||
|
||||
// asset methods
|
||||
if ($this->hasMethod($method)) {
|
||||
return $this->callMethod($method, $arguments);
|
||||
}
|
||||
|
||||
throw new BadMethodCallException('The method: "' . $method . '" does not exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique id for the asset
|
||||
*/
|
||||
|
|
|
@ -48,12 +48,16 @@ class Dir
|
|||
|
||||
/**
|
||||
* Copy the directory to a new destination
|
||||
*
|
||||
* @param array|false $ignore List of full paths to skip during copying
|
||||
* or `false` to copy all files, including
|
||||
* those listed in `Dir::$ignore`
|
||||
*/
|
||||
public static function copy(
|
||||
string $dir,
|
||||
string $target,
|
||||
bool $recursive = true,
|
||||
array $ignore = []
|
||||
array|false $ignore = []
|
||||
): bool {
|
||||
if (is_dir($dir) === false) {
|
||||
throw new Exception('The directory "' . $dir . '" does not exist');
|
||||
|
@ -67,10 +71,13 @@ class Dir
|
|||
throw new Exception('The target directory "' . $target . '" could not be created');
|
||||
}
|
||||
|
||||
foreach (static::read($dir) as $name) {
|
||||
foreach (static::read($dir, $ignore === false ? [] : null) as $name) {
|
||||
$root = $dir . '/' . $name;
|
||||
|
||||
if (in_array($root, $ignore) === true) {
|
||||
if (
|
||||
is_array($ignore) === true &&
|
||||
in_array($root, $ignore) === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -132,19 +139,32 @@ class Dir
|
|||
|
||||
/**
|
||||
* Read the directory and all subdirectories
|
||||
*
|
||||
* @todo Remove support for `$ignore = null` in a major release
|
||||
* @param array|false|null $ignore Array of absolut file paths;
|
||||
* `false` to disable `Dir::$ignore` list
|
||||
* (passing null is deprecated)
|
||||
*/
|
||||
public static function index(
|
||||
string $dir,
|
||||
bool $recursive = false,
|
||||
array|null $ignore = null,
|
||||
array|false|null $ignore = [],
|
||||
string $path = null
|
||||
): array {
|
||||
$result = [];
|
||||
$dir = realpath($dir);
|
||||
$items = static::read($dir);
|
||||
$items = static::read($dir, $ignore === false ? [] : null);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$root = $dir . '/' . $item;
|
||||
$root = $dir . '/' . $item;
|
||||
|
||||
if (
|
||||
is_array($ignore) === true &&
|
||||
in_array($root, $ignore) === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = $path !== null ? $path . '/' . $item : $item;
|
||||
$result[] = $entry;
|
||||
|
||||
|
|
|
@ -514,7 +514,14 @@ class F
|
|||
static::remove($newRoot);
|
||||
}
|
||||
|
||||
// actually move the file if it exists
|
||||
$directory = dirname($newRoot);
|
||||
|
||||
// create the parent directory if it does not exist
|
||||
if (is_dir($directory) === false) {
|
||||
Dir::make($directory, true);
|
||||
}
|
||||
|
||||
// actually move the file
|
||||
if (rename($oldRoot, $newRoot) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,10 @@ class Field extends Component
|
|||
public function __construct(string $type, array $attrs = [], ?Fields $formFields = null)
|
||||
{
|
||||
if (isset(static::$types[$type]) === false) {
|
||||
throw new InvalidArgumentException('The field type "' . $type . '" does not exist');
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'field.type.missing',
|
||||
'data' => ['name' => $attrs['name'] ?? '-', 'type' => $type]
|
||||
]);
|
||||
}
|
||||
|
||||
if (isset($attrs['model']) === false) {
|
||||
|
|
|
@ -202,6 +202,18 @@ class BlocksField extends FieldClass
|
|||
return $this->valueToJson($blocks, $this->pretty());
|
||||
}
|
||||
|
||||
protected function setDefault($default = null)
|
||||
{
|
||||
// set id for blocks if not exists
|
||||
if (is_array($default) === true) {
|
||||
array_walk($default, function (&$block) {
|
||||
$block['id'] ??= Str::uuid();
|
||||
});
|
||||
}
|
||||
|
||||
parent::setDefault($default);
|
||||
}
|
||||
|
||||
protected function setFieldsets($fieldsets, $model)
|
||||
{
|
||||
if (is_string($fieldsets) === true) {
|
||||
|
|
|
@ -115,6 +115,32 @@ class LayoutField extends BlocksField
|
|||
return $routes;
|
||||
}
|
||||
|
||||
protected function setDefault($default = null)
|
||||
{
|
||||
// set id for layouts, columns and blocks within layout if not exists
|
||||
if (is_array($default) === true) {
|
||||
array_walk($default, function (&$layout) {
|
||||
$layout['id'] ??= Str::uuid();
|
||||
|
||||
// set columns id within layout
|
||||
if (isset($layout['columns']) === true) {
|
||||
array_walk($layout['columns'], function (&$column) {
|
||||
$column['id'] ??= Str::uuid();
|
||||
|
||||
// set blocks id within column
|
||||
if (isset($column['blocks']) === true) {
|
||||
array_walk($column['blocks'], function (&$block) {
|
||||
$block['id'] ??= Str::uuid();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parent::setDefault($default);
|
||||
}
|
||||
|
||||
protected function setLayouts(array $layouts = [])
|
||||
{
|
||||
$this->layouts = array_map(
|
||||
|
|
|
@ -838,11 +838,13 @@ abstract class FieldClass
|
|||
*/
|
||||
protected function valueToJson(array $value = null, bool $pretty = false): string
|
||||
{
|
||||
$constants = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
|
||||
|
||||
if ($pretty === true) {
|
||||
return json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
$constants |= JSON_PRETTY_PRINT;
|
||||
}
|
||||
|
||||
return json_encode($value);
|
||||
return json_encode($value, $constants);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -324,9 +324,7 @@ class Form
|
|||
return $fields;
|
||||
}
|
||||
|
||||
if ($language === null) {
|
||||
$language = $kirby->language()->code();
|
||||
}
|
||||
$language ??= $kirby->language()->code();
|
||||
|
||||
if ($language !== $kirby->defaultLanguage()->code()) {
|
||||
foreach ($fields as $fieldName => $fieldProps) {
|
||||
|
|
|
@ -217,9 +217,6 @@ class Cookie
|
|||
{
|
||||
// lazily request the instance for non-CMS use cases
|
||||
$kirby = App::instance(null, true);
|
||||
|
||||
if ($kirby) {
|
||||
$kirby->response()->usesCookie($key);
|
||||
}
|
||||
$kirby?->response()->usesCookie($key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ class Environment
|
|||
*/
|
||||
public function detect(array $options = null, array $info = null): array
|
||||
{
|
||||
$info ??= $_SERVER;
|
||||
$info ??= $_SERVER;
|
||||
$options = array_merge([
|
||||
'cli' => null,
|
||||
'allowed' => null
|
||||
|
@ -195,7 +195,7 @@ class Environment
|
|||
* Sets the host name, port, path and protocol from the
|
||||
* fixed list of allowed URLs
|
||||
*/
|
||||
protected function detectAllowed(array|string|object $allowed): void
|
||||
protected function detectAllowed(array|string $allowed): void
|
||||
{
|
||||
$allowed = A::wrap($allowed);
|
||||
|
||||
|
@ -922,7 +922,7 @@ class Environment
|
|||
protected function sanitizeScriptPath(string|null $scriptPath = null): string
|
||||
{
|
||||
$scriptPath ??= '';
|
||||
$scriptPath = trim($scriptPath);
|
||||
$scriptPath = trim($scriptPath);
|
||||
|
||||
// skip all the sanitizing steps if the path is empty
|
||||
if ($scriptPath === '') {
|
||||
|
|
|
@ -101,8 +101,11 @@ class Header
|
|||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public static function type(string $mime, string $charset = 'UTF-8', bool $send = true)
|
||||
{
|
||||
public static function type(
|
||||
string $mime,
|
||||
string $charset = 'UTF-8',
|
||||
bool $send = true
|
||||
) {
|
||||
return static::contentType($mime, $charset, $send);
|
||||
}
|
||||
|
||||
|
@ -118,17 +121,25 @@ class Header
|
|||
* @return string|void
|
||||
* @psalm-return ($send is false ? string : void)
|
||||
*/
|
||||
public static function status(int|string|null $code = null, bool $send = true)
|
||||
{
|
||||
public static function status(
|
||||
int|string|null $code = null,
|
||||
bool $send = true
|
||||
) {
|
||||
$codes = static::$codes;
|
||||
$protocol = Environment::getGlobally('SERVER_PROTOCOL', 'HTTP/1.1');
|
||||
|
||||
// allow full control over code and message
|
||||
if (is_string($code) === true && preg_match('/^\d{3} \w.+$/', $code) === 1) {
|
||||
if (
|
||||
is_string($code) === true &&
|
||||
preg_match('/^\d{3} \w.+$/', $code) === 1
|
||||
) {
|
||||
$message = substr(rtrim($code), 4);
|
||||
$code = substr($code, 0, 3);
|
||||
} else {
|
||||
$code = array_key_exists('_' . $code, $codes) === false ? 500 : $code;
|
||||
if (array_key_exists('_' . $code, $codes) === false) {
|
||||
$code = 500;
|
||||
}
|
||||
|
||||
$message = $codes['_' . $code] ?? 'Something went wrong';
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class Params extends Obj
|
|||
$paramValue = $paramParts[1] ?? null;
|
||||
|
||||
if ($paramKey !== null) {
|
||||
$params[rawurldecode($paramKey)] = $paramValue ? rawurldecode($paramValue) : null;
|
||||
$params[rawurldecode($paramKey)] = $paramValue !== null ? rawurldecode($paramValue) : null;
|
||||
}
|
||||
|
||||
unset($path[$index]);
|
||||
|
@ -120,8 +120,10 @@ class Params extends Obj
|
|||
* Converts the params object to a params string
|
||||
* which can then be used in the URL builder again
|
||||
*/
|
||||
public function toString(bool $leadingSlash = false, bool $trailingSlash = false): string
|
||||
{
|
||||
public function toString(
|
||||
bool $leadingSlash = false,
|
||||
bool $trailingSlash = false
|
||||
): string {
|
||||
if ($this->isEmpty() === true) {
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -31,8 +31,10 @@ class Path extends Collection
|
|||
return $this->toString();
|
||||
}
|
||||
|
||||
public function toString(bool $leadingSlash = false, bool $trailingSlash = false): string
|
||||
{
|
||||
public function toString(
|
||||
bool $leadingSlash = false,
|
||||
bool $trailingSlash = false
|
||||
): string {
|
||||
if (empty($this->data) === true) {
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -91,7 +91,13 @@ class Remote
|
|||
|
||||
public static function __callStatic(string $method, array $arguments = []): static
|
||||
{
|
||||
return new static($arguments[0], array_merge(['method' => strtoupper($method)], $arguments[1] ?? []));
|
||||
return new static(
|
||||
url: $arguments[0],
|
||||
options: array_merge(
|
||||
['method' => strtoupper($method)],
|
||||
$arguments[1] ?? []
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,10 +182,10 @@ class Remote
|
|||
$headers = [];
|
||||
foreach ($this->options['headers'] as $key => $value) {
|
||||
if (is_string($key) === true) {
|
||||
$headers[] = $key . ': ' . $value;
|
||||
} else {
|
||||
$headers[] = $value;
|
||||
$value = $key . ': ' . $value;
|
||||
}
|
||||
|
||||
$headers[] = $value;
|
||||
}
|
||||
|
||||
$this->curlopt[CURLOPT_HTTPHEADER] = $headers;
|
||||
|
@ -264,7 +270,10 @@ class Remote
|
|||
$query = http_build_query($options['data']);
|
||||
|
||||
if (empty($query) === false) {
|
||||
$url = Url::hasQuery($url) === true ? $url . '&' . $query : $url . '?' . $query;
|
||||
$url = match (Url::hasQuery($url)) {
|
||||
true => $url . '&' . $query,
|
||||
default => $url . '?' . $query
|
||||
};
|
||||
}
|
||||
|
||||
// remove the data array from the options
|
||||
|
|
|
@ -147,9 +147,7 @@ class Request
|
|||
// this ensures that the response is only cached for
|
||||
// unauthenticated visitors;
|
||||
// https://github.com/getkirby/kirby/issues/4423#issuecomment-1166300526
|
||||
if ($kirby) {
|
||||
$kirby->response()->usesAuth(true);
|
||||
}
|
||||
$kirby?->response()->usesAuth(true);
|
||||
|
||||
if ($auth = $this->authString()) {
|
||||
$type = Str::lower(Str::before($auth, ' '));
|
||||
|
@ -292,7 +290,10 @@ class Request
|
|||
$headers = [];
|
||||
|
||||
foreach (Environment::getGlobally() as $key => $value) {
|
||||
if (substr($key, 0, 5) !== 'HTTP_' && substr($key, 0, 14) !== 'REDIRECT_HTTP_') {
|
||||
if (
|
||||
substr($key, 0, 5) !== 'HTTP_' &&
|
||||
substr($key, 0, 14) !== 'REDIRECT_HTTP_'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kirby\Http\Request;
|
||||
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* Base class for auth types
|
||||
*
|
||||
|
@ -22,8 +24,10 @@ abstract class Auth
|
|||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(string $data)
|
||||
{
|
||||
public function __construct(
|
||||
#[SensitiveParameter]
|
||||
string $data
|
||||
) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Kirby\Http\Request\Auth;
|
|||
|
||||
use Kirby\Http\Request\Auth;
|
||||
use Kirby\Toolkit\Str;
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* HTTP basic authentication data
|
||||
|
@ -20,8 +21,10 @@ class BasicAuth extends Auth
|
|||
protected string|null $password;
|
||||
protected string|null $username;
|
||||
|
||||
public function __construct(string $data)
|
||||
{
|
||||
public function __construct(
|
||||
#[SensitiveParameter]
|
||||
string $data
|
||||
) {
|
||||
parent::__construct($data);
|
||||
|
||||
$this->credentials = base64_decode($data);
|
||||
|
|
|
@ -22,7 +22,7 @@ class Files
|
|||
/**
|
||||
* Sanitized array of all received files
|
||||
*/
|
||||
protected array $files;
|
||||
protected array $files = [];
|
||||
|
||||
/**
|
||||
* Creates a new Files object
|
||||
|
@ -31,11 +31,7 @@ class Files
|
|||
*/
|
||||
public function __construct(array|null $files = null)
|
||||
{
|
||||
if ($files === null) {
|
||||
$files = $_FILES;
|
||||
}
|
||||
|
||||
$this->files = [];
|
||||
$files ??= $_FILES;
|
||||
|
||||
foreach ($files as $key => $file) {
|
||||
if (is_array($file['name'])) {
|
||||
|
|
|
@ -50,8 +50,13 @@ class Response
|
|||
/**
|
||||
* Creates a new response object
|
||||
*/
|
||||
public function __construct(string|array $body = '', string|null $type = null, int|null $code = null, array|null $headers = null, string|null $charset = null)
|
||||
{
|
||||
public function __construct(
|
||||
string|array $body = '',
|
||||
string|null $type = null,
|
||||
int|null $code = null,
|
||||
array|null $headers = null,
|
||||
string|null $charset = null
|
||||
) {
|
||||
// array construction
|
||||
if (is_array($body) === true) {
|
||||
$params = $body;
|
||||
|
@ -127,8 +132,11 @@ class Response
|
|||
*
|
||||
* @param array $props Custom overrides for response props (e.g. headers)
|
||||
*/
|
||||
public static function download(string $file, string|null $filename = null, array $props = []): static
|
||||
{
|
||||
public static function download(
|
||||
string $file,
|
||||
string|null $filename = null,
|
||||
array $props = []
|
||||
): static {
|
||||
if (file_exists($file) === false) {
|
||||
throw new Exception('The file could not be found');
|
||||
}
|
||||
|
@ -178,6 +186,7 @@ class Response
|
|||
* @since 3.7.0
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @todo Change return type to `never` once support for PHP 8.0 is dropped
|
||||
*/
|
||||
public static function go(string $url = '/', int $code = 302): void
|
||||
{
|
||||
|
@ -223,8 +232,12 @@ class Response
|
|||
* Creates a json response with appropriate
|
||||
* header and automatic conversion of arrays.
|
||||
*/
|
||||
public static function json(string|array $body = '', int|null $code = null, bool|null $pretty = null, array $headers = []): static
|
||||
{
|
||||
public static function json(
|
||||
string|array $body = '',
|
||||
int|null $code = null,
|
||||
bool|null $pretty = null,
|
||||
array $headers = []
|
||||
): static {
|
||||
if (is_array($body) === true) {
|
||||
$body = json_encode($body, $pretty === true ? JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES : 0);
|
||||
}
|
||||
|
|
|
@ -72,8 +72,12 @@ class Route
|
|||
* Creates a new Route object for the given
|
||||
* pattern(s), method(s) and the callback action
|
||||
*/
|
||||
public function __construct(string $pattern, string $method, Closure $action, array $attributes = [])
|
||||
{
|
||||
public function __construct(
|
||||
string $pattern,
|
||||
string $method,
|
||||
Closure $action,
|
||||
array $attributes = []
|
||||
) {
|
||||
$this->action = $action;
|
||||
$this->attributes = $attributes;
|
||||
$this->method = $method;
|
||||
|
|
|
@ -117,7 +117,10 @@ class Router
|
|||
if ($callback) {
|
||||
$result = $callback($route);
|
||||
} else {
|
||||
$result = $route?->action()->call($route, ...$route->arguments());
|
||||
$result = $route->action()->call(
|
||||
$route,
|
||||
...$route->arguments()
|
||||
);
|
||||
}
|
||||
|
||||
$loop = false;
|
||||
|
@ -139,8 +142,12 @@ class Router
|
|||
* the routing action immediately
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public static function execute(string|null $path = null, string $method = 'GET', array $routes = [], Closure|null $callback = null)
|
||||
{
|
||||
public static function execute(
|
||||
string|null $path = null,
|
||||
string $method = 'GET',
|
||||
array $routes = [],
|
||||
Closure|null $callback = null
|
||||
) {
|
||||
return (new static($routes))->call($path, $method, $callback);
|
||||
}
|
||||
|
||||
|
@ -150,8 +157,11 @@ class Router
|
|||
* find matches and return all the found
|
||||
* arguments in the path.
|
||||
*/
|
||||
public function find(string $path, string $method, array|null $ignore = null): Route|null
|
||||
{
|
||||
public function find(
|
||||
string $path,
|
||||
string $method,
|
||||
array|null $ignore = null
|
||||
): Route {
|
||||
if (isset($this->routes[$method]) === false) {
|
||||
throw new InvalidArgumentException('Invalid routing method: ' . $method, 400);
|
||||
}
|
||||
|
@ -163,7 +173,10 @@ class Router
|
|||
$arguments = $route->parse($route->pattern(), $path);
|
||||
|
||||
if ($arguments !== false) {
|
||||
if (empty($ignore) === true || in_array($route, $ignore) === false) {
|
||||
if (
|
||||
empty($ignore) === true ||
|
||||
in_array($route, $ignore) === false
|
||||
) {
|
||||
return $this->route = $route;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Kirby\Http;
|
|||
use Kirby\Cms\App;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use SensitiveParameter;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
|
@ -226,7 +227,10 @@ class Uri
|
|||
|
||||
$domain .= $this->host;
|
||||
|
||||
if ($this->port !== null && in_array($this->port, [80, 443]) === false) {
|
||||
if (
|
||||
$this->port !== null &&
|
||||
in_array($this->port, [80, 443]) === false
|
||||
) {
|
||||
$domain .= ':' . $this->port;
|
||||
}
|
||||
|
||||
|
@ -326,8 +330,10 @@ class Uri
|
|||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setPassword(string|null $password = null): static
|
||||
{
|
||||
public function setPassword(
|
||||
#[SensitiveParameter]
|
||||
string|null $password = null
|
||||
): static {
|
||||
$this->password = $password;
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ class Url
|
|||
*/
|
||||
public static function __callStatic(string $method, array $arguments)
|
||||
{
|
||||
return (new Uri($arguments[0] ?? static::current()))->$method(...array_slice($arguments, 1));
|
||||
$uri = new Uri($arguments[0] ?? static::current());
|
||||
return $uri->$method(...array_slice($arguments, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,7 +66,11 @@ class Url
|
|||
public static function fix(string|null $url = null): string|null
|
||||
{
|
||||
// make sure to not touch absolute urls
|
||||
return (!preg_match('!^(https|http|ftp)\:\/\/!i', $url ?? '')) ? 'http://' . $url : $url;
|
||||
if (!preg_match('!^(https|http|ftp)\:\/\/!i', $url ?? '')) {
|
||||
return 'http://' . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,7 +98,9 @@ class Url
|
|||
// //example.com/uri
|
||||
// http://example.com/uri, https://example.com/uri, ftp://example.com/uri
|
||||
// mailto:example@example.com, geo:49.0158,8.3239?z=11
|
||||
return $url !== null && preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:|geo:)!i', $url) === 1;
|
||||
return
|
||||
$url !== null &&
|
||||
preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:|geo:)!i', $url) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,8 +134,11 @@ class Url
|
|||
/**
|
||||
* Returns the path for the given url
|
||||
*/
|
||||
public static function path(string|array|null $url = null, bool $leadingSlash = false, bool $trailingSlash = false): string
|
||||
{
|
||||
public static function path(
|
||||
string|array|null $url = null,
|
||||
bool $leadingSlash = false,
|
||||
bool $trailingSlash = false
|
||||
): string {
|
||||
return Url::toObject($url)->path()->toString($leadingSlash, $trailingSlash);
|
||||
}
|
||||
|
||||
|
@ -151,8 +161,12 @@ class Url
|
|||
/**
|
||||
* Shortens the Url by removing all unnecessary parts
|
||||
*/
|
||||
public static function short(string|null $url = null, int $length = 0, bool $base = false, string $rep = '…'): string
|
||||
{
|
||||
public static function short(
|
||||
string|null $url = null,
|
||||
int $length = 0,
|
||||
bool $base = false,
|
||||
string $rep = '…'
|
||||
): string {
|
||||
$uri = static::toObject($url);
|
||||
|
||||
$uri->fragment = null;
|
||||
|
|
|
@ -33,7 +33,7 @@ class GdLib extends Darkroom
|
|||
$image = $this->blur($image, $options);
|
||||
$image = $this->grayscale($image, $options);
|
||||
|
||||
$image->toFile($file, $mime, $options['quality']);
|
||||
$image->toFile($file, $mime, $options);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
@ -60,7 +60,11 @@ class GdLib extends Darkroom
|
|||
return $image->resize($options['width'], $options['height']);
|
||||
}
|
||||
|
||||
return $image->thumbnail($options['width'], $options['height'] ?? $options['width'], $options['crop']);
|
||||
return $image->thumbnail(
|
||||
$options['width'],
|
||||
$options['height'] ?? $options['width'],
|
||||
$options['crop']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kirby\Image;
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The Dimension class is used to provide additional
|
||||
* methods for images and possibly other objects with
|
||||
|
@ -253,12 +255,28 @@ class Dimensions
|
|||
|
||||
if ($xml !== false) {
|
||||
$attr = $xml->attributes();
|
||||
$width = (int)($attr->width);
|
||||
$height = (int)($attr->height);
|
||||
if (($width === 0 || $height === 0) && empty($attr->viewBox) === false) {
|
||||
$box = explode(' ', $attr->viewBox);
|
||||
$width = (int)($box[2] ?? 0);
|
||||
$height = (int)($box[3] ?? 0);
|
||||
|
||||
$rawWidth = $attr->width;
|
||||
$width = (int)$rawWidth;
|
||||
$rawHeight = $attr->height;
|
||||
$height = (int)$rawHeight;
|
||||
|
||||
// use viewbox values if direct attributes are 0
|
||||
// or based on percentages
|
||||
if (empty($attr->viewBox) === false) {
|
||||
$box = explode(' ', $attr->viewBox);
|
||||
|
||||
// when using viewbox values, make sure to subtract
|
||||
// first two box values from last two box values
|
||||
// to retrieve the absolute dimensions
|
||||
|
||||
if (Str::endsWith($rawWidth, '%') === true || $width === 0) {
|
||||
$width = (int)($box[2] ?? 0) - (int)($box[0] ?? 0);
|
||||
}
|
||||
|
||||
if (Str::endsWith($rawHeight, '%') === true || $height === 0) {
|
||||
$height = (int)($box[3] ?? 0) - (int)($box[1] ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -215,9 +215,10 @@ class Exif
|
|||
*/
|
||||
protected function parseFocalLength(): string|null
|
||||
{
|
||||
return $this->data['FocalLength'] ??
|
||||
$this->data['FocalLengthIn35mmFilm'] ??
|
||||
null;
|
||||
return
|
||||
$this->data['FocalLength'] ??
|
||||
$this->data['FocalLengthIn35mmFilm'] ??
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kirby\Image;
|
||||
|
||||
use Kirby\Cms\Content;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Filesystem\File;
|
||||
use Kirby\Toolkit\Html;
|
||||
|
@ -112,6 +113,17 @@ class Image extends File
|
|||
*/
|
||||
public function html(array $attr = []): string
|
||||
{
|
||||
// if no alt text explicitly provided,
|
||||
// try to infer from model content file
|
||||
if (
|
||||
$this->model !== null &&
|
||||
method_exists($this->model, 'content') === true &&
|
||||
$this->model->content() instanceof Content &&
|
||||
$this->model->content()->get('alt')->isNotEmpty() === true
|
||||
) {
|
||||
$attr['alt'] ??= $this->model->content()->get('alt')->value();
|
||||
}
|
||||
|
||||
if ($url = $this->url()) {
|
||||
return Html::img($url, $attr);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use Kirby\Blueprint\NodeText;
|
|||
use Kirby\Cms\ModelWithContent;
|
||||
|
||||
/**
|
||||
* Option for select fields, radio fields, etc
|
||||
* Option for select fields, radio fields, etc.
|
||||
*
|
||||
* @package Kirby Option
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
|
@ -19,7 +19,7 @@ use Kirby\Cms\ModelWithContent;
|
|||
class Option
|
||||
{
|
||||
public function __construct(
|
||||
public float|int|string|null $value,
|
||||
public string|int|float|null $value,
|
||||
public bool $disabled = false,
|
||||
public NodeIcon|null $icon = null,
|
||||
public NodeText|null $info = null,
|
||||
|
@ -28,7 +28,7 @@ class Option
|
|||
$this->text ??= new NodeText(['en' => $this->value]);
|
||||
}
|
||||
|
||||
public static function factory(float|int|string|null|array $props): static
|
||||
public static function factory(string|int|float|null|array $props): static
|
||||
{
|
||||
if (is_array($props) === false) {
|
||||
$props = ['value' => $props];
|
||||
|
|
|
@ -6,7 +6,8 @@ use Kirby\Blueprint\Collection;
|
|||
use Kirby\Cms\ModelWithContent;
|
||||
|
||||
/**
|
||||
* Options
|
||||
* Collection of possible options for
|
||||
* select fields, radio fields, etc.
|
||||
*
|
||||
* @package Kirby Option
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
|
@ -30,6 +31,7 @@ class Options extends Collection
|
|||
$collection = new static();
|
||||
|
||||
foreach ($items as $key => $option) {
|
||||
// convert an associative value => text array into props;
|
||||
// skip if option is already an array of option props
|
||||
if (
|
||||
is_array($option) === false ||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kirby\Option;
|
||||
|
||||
use Kirby\Cms\Field;
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Cms\Nest;
|
||||
use Kirby\Data\Json;
|
||||
|
@ -88,8 +89,12 @@ class OptionsApi extends OptionsProvider
|
|||
* Creates the actual options by loading
|
||||
* data from the API and resolving it to
|
||||
* the correct text-value entries
|
||||
*
|
||||
* @param bool $safeMode Whether to escape special HTML characters in
|
||||
* the option text for safe output in the Panel;
|
||||
* only set to `false` if the text is later escaped!
|
||||
*/
|
||||
public function resolve(ModelWithContent $model): Options
|
||||
public function resolve(ModelWithContent $model, bool $safeMode = true): Options
|
||||
{
|
||||
// use cached options if present
|
||||
// @codeCoverageIgnoreStart
|
||||
|
@ -101,25 +106,41 @@ class OptionsApi extends OptionsProvider
|
|||
// apply property defaults
|
||||
$this->defaults();
|
||||
|
||||
// load data from URL and narrow down to queried part
|
||||
// load data from URL and convert from JSON to array
|
||||
$data = $this->load($model);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($data === null) {
|
||||
throw new NotFoundException('Options could not be loaded from API: ' . $model->toSafeString($this->url));
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// turn data into Nest so that it can be queried
|
||||
// or field methods applied to the data
|
||||
$data = Nest::create($data);
|
||||
$data = Query::factory($this->query)->resolve($data);
|
||||
|
||||
// optionally query a substructure inside the data array
|
||||
$data = Query::factory($this->query)->resolve($data);
|
||||
$options = [];
|
||||
|
||||
// create options by resolving text and value query strings
|
||||
// for each item from the data
|
||||
$options = $data->toArray(fn ($item) => [
|
||||
// value is always a raw string
|
||||
'value' => $model->toString($this->value, ['item' => $item]),
|
||||
// text is only a raw string when using {< >}
|
||||
'text' => $model->toSafeString($this->text, ['item' => $item]),
|
||||
]);
|
||||
foreach ($data as $key => $item) {
|
||||
// convert simple `key: value` API data
|
||||
if (is_string($item) === true) {
|
||||
$item = new Field(null, $key, $item);
|
||||
}
|
||||
|
||||
$safeMethod = $safeMode === true ? 'toSafeString' : 'toString';
|
||||
|
||||
$options[] = [
|
||||
// value is always a raw string
|
||||
'value' => $model->toString($this->value, ['item' => $item]),
|
||||
// text is only a raw string when using {< >}
|
||||
// or when the safe mode is explicitly disabled (select field)
|
||||
'text' => $model->$safeMethod($this->text, ['item' => $item])
|
||||
];
|
||||
}
|
||||
|
||||
// create Options object and render this subsequently
|
||||
return $this->options = Options::factory($options);
|
||||
|
|
|
@ -26,5 +26,13 @@ abstract class OptionsProvider
|
|||
return $this->resolve($model)->render($model);
|
||||
}
|
||||
|
||||
abstract public function resolve(ModelWithContent $model): Options;
|
||||
/**
|
||||
* Dynamically determines the actual options and resolves
|
||||
* them to the correct text-value entries
|
||||
*
|
||||
* @param bool $safeMode Whether to escape special HTML characters in
|
||||
* the option text for safe output in the Panel;
|
||||
* only set to `false` if the text is later escaped!
|
||||
*/
|
||||
abstract public function resolve(ModelWithContent $model, bool $safeMode = true): Options;
|
||||
}
|
||||
|
|
|
@ -131,8 +131,12 @@ class OptionsQuery extends OptionsProvider
|
|||
* Creates the actual options by running
|
||||
* the query on the model and resolving it to
|
||||
* the correct text-value entries
|
||||
*
|
||||
* @param bool $safeMode Whether to escape special HTML characters in
|
||||
* the option text for safe output in the Panel;
|
||||
* only set to `false` if the text is later escaped!
|
||||
*/
|
||||
public function resolve(ModelWithContent $model): Options
|
||||
public function resolve(ModelWithContent $model, bool $safeMode = true): Options
|
||||
{
|
||||
// use cached options if present
|
||||
// @codeCoverageIgnoreStart
|
||||
|
@ -159,7 +163,7 @@ class OptionsQuery extends OptionsProvider
|
|||
}
|
||||
|
||||
// create options array
|
||||
$options = $result->toArray(function ($item) use ($model) {
|
||||
$options = $result->toArray(function ($item) use ($model, $safeMode) {
|
||||
// get defaults based on item type
|
||||
[$alias, $text, $value] = $this->itemToDefaults($item);
|
||||
$data = ['item' => $item, $alias => $item];
|
||||
|
@ -167,9 +171,10 @@ class OptionsQuery extends OptionsProvider
|
|||
// value is always a raw string
|
||||
$value = $model->toString($this->value ?? $value, $data);
|
||||
|
||||
// text is only a raw string when HTML prop
|
||||
// is explicitly set to true
|
||||
$text = $model->toSafeString($this->text ?? $text, $data);
|
||||
// text is only a raw string when using {< >}
|
||||
// or when the safe mode is explicitly disabled (select field)
|
||||
$safeMethod = $safeMode === true ? 'toSafeString' : 'toString';
|
||||
$text = $model->$safeMethod($this->text ?? $text, $data);
|
||||
|
||||
return compact('text', 'value');
|
||||
});
|
||||
|
|
|
@ -198,7 +198,9 @@ class Document
|
|||
*/
|
||||
public static function icons(): string
|
||||
{
|
||||
return F::read(App::instance()->root('kirby') . '/panel/dist/img/icons.svg');
|
||||
$dev = App::instance()->option('panel.dev', false);
|
||||
$dir = $dev ? 'public' : 'dist';
|
||||
return F::read(App::instance()->root('kirby') . '/panel/' . $dir . '/img/icons.svg');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -196,9 +196,10 @@ class File extends Model
|
|||
'rtf' => 'blue-400'
|
||||
];
|
||||
|
||||
return $extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
parent::imageDefaults()['color'];
|
||||
return
|
||||
$extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
parent::imageDefaults()['color'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -237,9 +238,10 @@ class File extends Model
|
|||
'md' => 'markdown'
|
||||
];
|
||||
|
||||
return $extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
'file';
|
||||
return
|
||||
$extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
'file';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -292,17 +294,22 @@ class File extends Model
|
|||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$name = $this->model->filename();
|
||||
$id = $this->model->id();
|
||||
|
||||
if (empty($params['model']) === false) {
|
||||
$parent = $this->model->parent();
|
||||
// if the file belongs to the current parent model,
|
||||
// store only name as ID to keep its path relative to the model
|
||||
$id = $parent === $params['model'] ? $name : $id;
|
||||
$absolute = $parent !== $params['model'];
|
||||
}
|
||||
|
||||
$params['text'] ??= '{{ file.filename }}';
|
||||
|
||||
return array_merge(parent::pickerData($params), [
|
||||
'filename' => $name,
|
||||
'dragText' => $this->dragText('auto', $absolute ?? false),
|
||||
'filename' => $name,
|
||||
'id' => $id,
|
||||
'type' => $this->model->type(),
|
||||
'url' => $this->model->url()
|
||||
]);
|
||||
|
|
|
@ -196,10 +196,7 @@ class Page extends Model
|
|||
protected function imageSource(
|
||||
string|null $query = null
|
||||
): CmsFile|Asset|null {
|
||||
if ($query === null) {
|
||||
$query = 'page.image';
|
||||
}
|
||||
|
||||
$query ??= 'page.image';
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
|
||||
|
@ -234,8 +231,9 @@ class Page extends Model
|
|||
*/
|
||||
public function position(): int
|
||||
{
|
||||
return $this->model->num() ??
|
||||
$this->model->parentModel()->children()->listed()->not($this->model)->count() + 1;
|
||||
return
|
||||
$this->model->num() ??
|
||||
$this->model->parentModel()->children()->listed()->not($this->model)->count() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,10 +38,7 @@ class Site extends Model
|
|||
protected function imageSource(
|
||||
string|null $query = null
|
||||
): CmsFile|Asset|null {
|
||||
if ($query === null) {
|
||||
$query = 'site.image';
|
||||
}
|
||||
|
||||
$query ??= 'site.image';
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ class Element
|
|||
* Tries to find a single nested element by
|
||||
* query and otherwise returns null
|
||||
*/
|
||||
public function find(string $query): Element|null
|
||||
public function find(string $query): static|null
|
||||
{
|
||||
if ($result = $this->query($query)[0]) {
|
||||
return new static($result);
|
||||
|
@ -107,6 +107,8 @@ class Element
|
|||
|
||||
/**
|
||||
* Returns the inner HTML of the element
|
||||
*
|
||||
* @param array|null $marks List of allowed marks
|
||||
*/
|
||||
public function innerHtml(array|null $marks = null): string
|
||||
{
|
||||
|
|
|
@ -38,18 +38,23 @@ class Argument
|
|||
$argument = trim(substr($argument, 1, -1));
|
||||
}
|
||||
|
||||
// string with single or double quotes
|
||||
// string with single quotes
|
||||
if (
|
||||
(
|
||||
Str::startsWith($argument, '"') &&
|
||||
Str::endsWith($argument, '"')
|
||||
) || (
|
||||
Str::startsWith($argument, "'") &&
|
||||
Str::endsWith($argument, "'")
|
||||
)
|
||||
Str::startsWith($argument, "'") &&
|
||||
Str::endsWith($argument, "'")
|
||||
) {
|
||||
$string = substr($argument, 1, -1);
|
||||
$string = str_replace(['\"', "\'"], ['"', "'"], $string);
|
||||
$string = str_replace("\'", "'", $string);
|
||||
return new static($string);
|
||||
}
|
||||
|
||||
// string with double quotes
|
||||
if (
|
||||
Str::startsWith($argument, '"') &&
|
||||
Str::endsWith($argument, '"')
|
||||
) {
|
||||
$string = substr($argument, 1, -1);
|
||||
$string = str_replace('\"', '"', $string);
|
||||
return new static($string);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ use Kirby\Toolkit\A;
|
|||
use Kirby\Toolkit\Collection;
|
||||
|
||||
/**
|
||||
* The Argument class represents a single
|
||||
* parameter passed to a method in a chained query
|
||||
* The Arguments class helps splitting a
|
||||
* parameter string into processable arguments
|
||||
*
|
||||
* @package Kirby Query
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
|
@ -26,8 +26,9 @@ class Arguments extends Collection
|
|||
// skip all matches inside of single quotes
|
||||
public const NO_SLQU = '\'(?:[^\'\\\\]|\\\\.)*\'(*SKIP)(*FAIL)';
|
||||
// skip all matches inside of any of the above skip groups
|
||||
public const OUTSIDE = self::NO_PNTH . '|' . self::NO_SQBR . '|' .
|
||||
self::NO_DLQU . '|' . self::NO_SLQU;
|
||||
public const OUTSIDE =
|
||||
self::NO_PNTH . '|' . self::NO_SQBR . '|' .
|
||||
self::NO_DLQU . '|' . self::NO_SLQU;
|
||||
|
||||
/**
|
||||
* Splits list of arguments into individual
|
||||
|
|
|
@ -22,6 +22,9 @@ class Expression
|
|||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an expression string into its parts
|
||||
*/
|
||||
public static function factory(string $expression, Query $parent = null): static|Segments
|
||||
{
|
||||
// split into different expression parts and operators
|
||||
|
|
|
@ -51,7 +51,7 @@ class Query
|
|||
/**
|
||||
* Creates a new Query object
|
||||
*/
|
||||
public static function factory(string $query): static
|
||||
public static function factory(string|null $query): static
|
||||
{
|
||||
return new static(query: $query);
|
||||
}
|
||||
|
@ -124,7 +124,6 @@ Query::$entries['site'] = function (): Site {
|
|||
return App::instance()->site();
|
||||
};
|
||||
|
||||
|
||||
Query::$entries['t'] = function (
|
||||
string $key,
|
||||
string|array $fallback = null,
|
||||
|
|
|
@ -51,6 +51,11 @@ class Segment
|
|||
throw new BadMethodCallException($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a segment into the property/method name and its arguments
|
||||
*
|
||||
* @param int $position String position of the segment inside the full query
|
||||
*/
|
||||
public static function factory(
|
||||
string $segment,
|
||||
int $position = 0
|
||||
|
@ -69,6 +74,10 @@ class Segment
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically resolves the segment depending on the
|
||||
* segment position and the type of the base
|
||||
*/
|
||||
public function resolve(mixed $base = null, array|object $data = []): mixed
|
||||
{
|
||||
// resolve arguments to array
|
||||
|
|
|
@ -81,8 +81,9 @@ class Segments extends Collection
|
|||
return null;
|
||||
}
|
||||
|
||||
// for regular connectors, just skip
|
||||
if ($segment === '.') {
|
||||
// for regular connectors and optional chaining on non-null,
|
||||
// just skip this connecting segment
|
||||
if ($segment === '.' || $segment === '?.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,8 +56,9 @@ class Sane
|
|||
|
||||
// find a handler or alias
|
||||
$alias = static::$aliases[$type] ?? null;
|
||||
$handler = static::$handlers[$type] ??
|
||||
($alias ? static::$handlers[$alias] ?? null : null);
|
||||
$handler =
|
||||
static::$handlers[$type] ??
|
||||
($alias ? static::$handlers[$alias] ?? null : null);
|
||||
|
||||
if (empty($handler) === false && class_exists($handler) === true) {
|
||||
return new $handler();
|
||||
|
|
|
@ -339,7 +339,11 @@ class Session
|
|||
* @todo The $this->destroyed check gets flagged by Psalm for unknown reasons
|
||||
* @psalm-suppress ParadoxicalCondition
|
||||
*/
|
||||
if ($this->writeMode !== true || $this->tokenExpiry === null || $this->destroyed === true) {
|
||||
if (
|
||||
$this->writeMode !== true ||
|
||||
$this->tokenExpiry === null ||
|
||||
$this->destroyed === true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -523,7 +527,11 @@ class Session
|
|||
* @todo The $this->destroyed check gets flagged by Psalm for unknown reasons
|
||||
* @psalm-suppress ParadoxicalCondition
|
||||
*/
|
||||
if ($this->tokenExpiry === null || $this->destroyed === true || $this->writeMode === true) {
|
||||
if (
|
||||
$this->tokenExpiry === null ||
|
||||
$this->destroyed === true ||
|
||||
$this->writeMode === true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
123
kirby/src/Template/Slot.php
Normal file
123
kirby/src/Template/Slot.php
Normal file
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Template;
|
||||
|
||||
use Kirby\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* The slot class catches all content
|
||||
* between the beginning and the end of
|
||||
* a slot. Slot content is then stored
|
||||
* in the Slots collection.
|
||||
*
|
||||
* @package Kirby Template
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Slot
|
||||
{
|
||||
/**
|
||||
* The captured slot content
|
||||
* @internal
|
||||
*/
|
||||
public string|null $content;
|
||||
|
||||
/**
|
||||
* The name that was declared during
|
||||
* the definition of the slot
|
||||
*/
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* Keeps track of the slot state
|
||||
*/
|
||||
protected bool $open = false;
|
||||
|
||||
/**
|
||||
* Creates a new slot
|
||||
*/
|
||||
public function __construct(string $name, string|null $content = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the slot content or an empty string
|
||||
* if the slot is empty.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->render() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the slot helper
|
||||
*/
|
||||
public static function begin(string $name = 'default'): static|null
|
||||
{
|
||||
return Snippet::$current?->slot($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a slot and catches all the content
|
||||
* that has been printed since the slot has
|
||||
* been opened
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if ($this->open === false) {
|
||||
throw new LogicException('The slot has not been opened');
|
||||
}
|
||||
|
||||
$this->content = ob_get_clean();
|
||||
$this->open = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the endslot() helper
|
||||
*/
|
||||
public static function end(): void
|
||||
{
|
||||
Snippet::$current?->endslot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the slot is currently
|
||||
* open and being buffered
|
||||
*/
|
||||
public function isOpen(): bool
|
||||
{
|
||||
return $this->open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the slot name
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the slot and starts
|
||||
* output buffering
|
||||
*/
|
||||
public function open(): void
|
||||
{
|
||||
$this->open = true;
|
||||
|
||||
// capture the output
|
||||
ob_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the slot content
|
||||
*/
|
||||
public function render(): string|null
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
53
kirby/src/Template/Slots.php
Normal file
53
kirby/src/Template/Slots.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Template;
|
||||
|
||||
use Countable;
|
||||
|
||||
/**
|
||||
* The slots collection is simplifying
|
||||
* slot access. Slots can be accessed with
|
||||
* `$slots->heading()` and accessing a non-existing
|
||||
* slot will simply return null.
|
||||
*
|
||||
* @package Kirby Template
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Slots implements Countable
|
||||
{
|
||||
/**
|
||||
* Creates a new slots collection
|
||||
*/
|
||||
public function __construct(protected array $slots)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter for slots;
|
||||
* e.g. `$slots->heading`
|
||||
*/
|
||||
public function __get(string $name): Slot|null
|
||||
{
|
||||
return $this->slots[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter method for slots;
|
||||
* e.g. `$slots->heading()`
|
||||
*/
|
||||
public function __call(string $name, array $args): Slot|null
|
||||
{
|
||||
return $this->__get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of defined slots
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->slots);
|
||||
}
|
||||
}
|
324
kirby/src/Template/Snippet.php
Normal file
324
kirby/src/Template/Snippet.php
Normal file
|
@ -0,0 +1,324 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Template;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Helpers;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Tpl;
|
||||
|
||||
/**
|
||||
* The Snippet class includes shared code parts
|
||||
* in templates and allows to pass data as well as to
|
||||
* optionally pass content to various predefined slots.
|
||||
*
|
||||
* @package Kirby Template
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>,
|
||||
* Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Snippet extends Tpl
|
||||
{
|
||||
/**
|
||||
* Cache for the currently active
|
||||
* snippet. This is used to start
|
||||
* and end slots within this snippet
|
||||
* in the helper functions
|
||||
* @internal
|
||||
*/
|
||||
public static self|null $current = null;
|
||||
|
||||
/**
|
||||
* Contains all slots that are opened
|
||||
* but not yet closed
|
||||
*/
|
||||
protected array $capture = [];
|
||||
|
||||
/**
|
||||
* Associative array with variables that
|
||||
* will be set inside the snippet
|
||||
*/
|
||||
protected array $data;
|
||||
|
||||
/**
|
||||
* An empty dummy slots object used for snippets
|
||||
* that were loaded without passing slots
|
||||
*/
|
||||
protected static Slots|null $dummySlots = null;
|
||||
|
||||
/**
|
||||
* Full path to the PHP file of the snippet;
|
||||
* can be `null` for "dummy" snippets that don't exist
|
||||
*/
|
||||
protected string|null $file;
|
||||
|
||||
/**
|
||||
* Keeps track of the state of the snippet
|
||||
*/
|
||||
protected bool $open = false;
|
||||
|
||||
/**
|
||||
* The parent snippet
|
||||
*/
|
||||
protected self|null $parent = null;
|
||||
|
||||
/**
|
||||
* The collection of closed slots that will be used
|
||||
* to pass down to the template for the snippet.
|
||||
*/
|
||||
protected array $slots = [];
|
||||
|
||||
/**
|
||||
* Creates a new snippet
|
||||
*/
|
||||
public function __construct(string|null $file, array $data = [])
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and opens a new snippet. This can be used
|
||||
* directly in a template or via the slots() helper
|
||||
*/
|
||||
public static function begin(string|null $file, array $data = []): static
|
||||
{
|
||||
$snippet = new static($file, $data);
|
||||
return $snippet->open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the snippet and catches
|
||||
* the default slot if no slots have been
|
||||
* defined in between opening and closing.
|
||||
*/
|
||||
public function close(): static
|
||||
{
|
||||
// make sure that ending a snippet
|
||||
// is only supported if the snippet has
|
||||
// been started before
|
||||
if ($this->open === false) {
|
||||
throw new LogicException('The snippet has not been opened');
|
||||
}
|
||||
|
||||
// create a default slot for the content
|
||||
// that has been captured between start and end
|
||||
if (empty($this->slots) === true) {
|
||||
$this->slots['default'] = new Slot('default');
|
||||
$this->slots['default']->content = ob_get_clean();
|
||||
} else {
|
||||
// swallow any "unslotted" content
|
||||
// between start and end
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
$this->open = false;
|
||||
|
||||
// switch back to the parent in nested
|
||||
// snippet stacks
|
||||
static::$current = $this->parent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the endsnippet() helper
|
||||
*/
|
||||
public static function end(): void
|
||||
{
|
||||
echo static::$current?->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the last openend slot
|
||||
*/
|
||||
public function endslot(): void
|
||||
{
|
||||
// take the last slot from the capture stack
|
||||
$slot = array_pop($this->capture);
|
||||
|
||||
// capture the content and close the slot
|
||||
$slot->close();
|
||||
|
||||
// add the slot to the scope
|
||||
$this->slots[$slot->name()] = $slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either an open snippet capturing slots
|
||||
* or the template string for self-enclosed snippets
|
||||
*/
|
||||
public static function factory(
|
||||
string|array|null $name,
|
||||
array $data = [],
|
||||
bool $slots = false
|
||||
): static|string {
|
||||
// instead of returning empty string when `$name` is null
|
||||
// allow rest of code to run, otherwise the wrong snippet would be closed
|
||||
// and potential issues for nested snippets may occur
|
||||
$file = $name !== null ? static::file($name) : null;
|
||||
|
||||
// for snippets with slots, make sure to open a new
|
||||
// snippet and start capturing slots
|
||||
if ($slots === true) {
|
||||
return static::begin($file, $data);
|
||||
}
|
||||
|
||||
// for snippets without slots, directly load and return
|
||||
// the snippet's template file
|
||||
return static::load($file, static::scope($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute path to the file for
|
||||
* the snippet/s taking snippets defined in plugins
|
||||
* into account
|
||||
*/
|
||||
public static function file(string|array $name): string|null
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$root = static::root();
|
||||
$names = A::wrap($name);
|
||||
|
||||
foreach ($names as $name) {
|
||||
$name = (string)$name;
|
||||
$file = $root . '/' . $name . '.php';
|
||||
|
||||
if (file_exists($file) === false) {
|
||||
$file = $kirby->extensions('snippets')[$name] ?? null;
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the snippet and starts output
|
||||
* buffering to catch all slots in between
|
||||
*/
|
||||
public function open(): static
|
||||
{
|
||||
if (static::$current !== null) {
|
||||
$this->parent = static::$current;
|
||||
}
|
||||
|
||||
$this->open = true;
|
||||
static::$current = $this;
|
||||
|
||||
ob_start();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent snippet if it exists
|
||||
*/
|
||||
public function parent(): static|null
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the snippet and passes the scope
|
||||
* with all slots and data
|
||||
*/
|
||||
public function render(array $data = [], array $slots = []): string
|
||||
{
|
||||
// always make sure that the snippet
|
||||
// is closed before it can be rendered
|
||||
if ($this->open === true) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
// manually add slots
|
||||
foreach ($slots as $slotName => $slotContent) {
|
||||
$this->slots[$slotName] = new Slot($slotName, $slotContent);
|
||||
}
|
||||
|
||||
// custom data overrides for the data that was passed to the snippet instance
|
||||
$data = array_replace_recursive($this->data, $data);
|
||||
|
||||
return static::load($this->file, static::scope($data, $this->slots()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root directory for all
|
||||
* snippet templates
|
||||
*/
|
||||
public static function root(): string
|
||||
{
|
||||
return App::instance()->root('snippets');
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new slot with the given name
|
||||
*/
|
||||
public function slot(string $name = 'default'): Slot
|
||||
{
|
||||
$slot = new Slot($name);
|
||||
$slot->open();
|
||||
|
||||
// start a new slot
|
||||
$this->capture[] = $slot;
|
||||
|
||||
return $slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the slots collection
|
||||
*/
|
||||
public function slots(): Slots
|
||||
{
|
||||
return new Slots($this->slots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data variables that get passed to a snippet
|
||||
*
|
||||
* @param \Kirby\Template\Slots|null $slots If null, an empty dummy object is used
|
||||
*/
|
||||
protected static function scope(array $data = [], Slots|null $slots = null): array
|
||||
{
|
||||
// initialize a dummy slots object and cache it for better performance
|
||||
if ($slots === null) {
|
||||
$slots = static::$dummySlots ??= new Slots([]);
|
||||
}
|
||||
|
||||
$data = array_merge(App::instance()->data, $data);
|
||||
|
||||
// TODO 3.10: Replace the following code:
|
||||
// if (
|
||||
// array_key_exists('slot', $data) === true ||
|
||||
// array_key_exists('slots', $data) === true
|
||||
// ) {
|
||||
// throw new InvalidArgumentException('Passing the $slot or $slots variables to snippets is not supported.');
|
||||
// }
|
||||
//
|
||||
// return array_merge($data, [
|
||||
// 'slot' => $slots->default,
|
||||
// 'slots' => $slots,
|
||||
// ]);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if (
|
||||
array_key_exists('slot', $data) === true ||
|
||||
array_key_exists('slots', $data) === true
|
||||
) {
|
||||
Helpers::deprecated('Passing the $slot or $slots variables to snippets is deprecated and will break in a future version.', 'snippet-pass-slots');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return array_merge([
|
||||
'slot' => $slots->default,
|
||||
'slots' => $slots,
|
||||
], $data);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
namespace Kirby\Template;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Tpl;
|
||||
|
||||
|
@ -10,7 +11,7 @@ use Kirby\Toolkit\Tpl;
|
|||
* Represents a Kirby template and takes care
|
||||
* of loading the correct file.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @package Kirby Template
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
|
@ -20,38 +21,26 @@ class Template
|
|||
{
|
||||
/**
|
||||
* Global template data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $data = [];
|
||||
|
||||
/**
|
||||
* The name of the template
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Template type (html, json, etc.)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
public static array $data = [];
|
||||
|
||||
/**
|
||||
* Default template type if no specific type is set
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultType;
|
||||
protected string $defaultType;
|
||||
|
||||
/**
|
||||
* The name of the template
|
||||
*/
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* Template type (html, json, etc.)
|
||||
*/
|
||||
protected string $type;
|
||||
|
||||
/**
|
||||
* Creates a new template object
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $defaultType
|
||||
*/
|
||||
public function __construct(string $name, string $type = 'html', string $defaultType = 'html')
|
||||
{
|
||||
|
@ -63,18 +52,22 @@ class Template
|
|||
/**
|
||||
* Converts the object to a simple string
|
||||
* This is used in template filters for example
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default template type
|
||||
*/
|
||||
public function defaultType(): string
|
||||
{
|
||||
return $this->defaultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the template exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
|
@ -87,75 +80,61 @@ class Template
|
|||
|
||||
/**
|
||||
* Returns the expected template file extension
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function extension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default template type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function defaultType(): string
|
||||
{
|
||||
return $this->defaultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the place where templates are located
|
||||
* in the site folder and and can be found in extensions
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function store(): string
|
||||
{
|
||||
return 'templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the location of the template file
|
||||
* if it exists.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function file(): string|null
|
||||
{
|
||||
$name = $this->name();
|
||||
$extension = $this->extension();
|
||||
$store = $this->store();
|
||||
$root = $this->root();
|
||||
|
||||
if ($this->hasDefaultType() === true) {
|
||||
try {
|
||||
// Try the default template in the default template directory.
|
||||
return F::realpath($this->root() . '/' . $this->name() . '.' . $this->extension(), $this->root());
|
||||
return F::realpath($root . '/' . $name . '.' . $extension, $root);
|
||||
} catch (Exception) {
|
||||
// ignore errors, continue searching
|
||||
}
|
||||
|
||||
// Look for the default template provided by an extension.
|
||||
$path = App::instance()->extension($this->store(), $this->name());
|
||||
$path = App::instance()->extension($store, $name);
|
||||
|
||||
if ($path !== null) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
$name = $this->name() . '.' . $this->type();
|
||||
$name .= '.' . $this->type();
|
||||
|
||||
try {
|
||||
// Try the template with type extension in the default template directory.
|
||||
return F::realpath($this->root() . '/' . $name . '.' . $this->extension(), $this->root());
|
||||
return F::realpath($root . '/' . $name . '.' . $extension, $root);
|
||||
} catch (Exception) {
|
||||
// Look for the template with type extension provided by an extension.
|
||||
// This might be null if the template does not exist.
|
||||
return App::instance()->extension($this->store(), $name);
|
||||
return App::instance()->extension($store, $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the template uses the default type
|
||||
*/
|
||||
public function hasDefaultType(): bool
|
||||
{
|
||||
return $this->type() === $this->defaultType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
|
@ -163,43 +142,67 @@ class Template
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return string
|
||||
* Renders the template with the given template data
|
||||
*/
|
||||
public function render(array $data = []): string
|
||||
{
|
||||
return Tpl::load($this->file(), $data);
|
||||
// if the template is rendered inside a snippet,
|
||||
// we need to keep the "outside" snippet object
|
||||
// to compare it later
|
||||
$snippet = Snippet::$current;
|
||||
|
||||
// load the template
|
||||
$template = Tpl::load($this->file(), $data);
|
||||
|
||||
// if last `endsnippet()` inside the current template
|
||||
// has been omitted (= snippet was used as layout snippet),
|
||||
// `Snippet::$current` will point to a snippet that was
|
||||
// opened inside the template; if that snippet is the direct
|
||||
// child of the snippet that was open before the template was
|
||||
// rendered (which could be `null` if no snippet was open),
|
||||
// take the buffer output from the template as default slot
|
||||
// and render the snippet as final template output
|
||||
if (
|
||||
Snippet::$current === null ||
|
||||
Snippet::$current->parent() !== $snippet
|
||||
) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
// no slots have been defined, but the template code
|
||||
// should be used as default slot
|
||||
if (Snippet::$current->slots()->count() === 0) {
|
||||
return Snippet::$current->render($data, [
|
||||
'default' => $template
|
||||
]);
|
||||
}
|
||||
|
||||
// let the snippet close and render natively
|
||||
return Snippet::$current->render($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root to the templates directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function root(): string
|
||||
{
|
||||
return App::instance()->root($this->store());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the place where templates are located
|
||||
* in the site folder and and can be found in extensions
|
||||
*/
|
||||
public function store(): string
|
||||
{
|
||||
return 'templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the template uses the default type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDefaultType(): bool
|
||||
{
|
||||
$type = $this->type();
|
||||
|
||||
return $type === null || $type === $this->defaultType();
|
||||
}
|
||||
}
|
|
@ -148,6 +148,7 @@ class KirbyTag
|
|||
|
||||
return $this->kirby()->file($path, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Kirby instance
|
||||
*/
|
||||
|
|
|
@ -25,6 +25,9 @@ class KirbyTags
|
|||
array $data = [],
|
||||
array $options = []
|
||||
): string {
|
||||
// make sure $text is a string
|
||||
$text ??= '';
|
||||
|
||||
$regex = '!
|
||||
(?=[^\]]) # positive lookahead that matches a group after the main expression without including ] in the result
|
||||
(?=\([a-z0-9_-]+:) # positive lookahead that requires starts with ( and lowercase ASCII letters, digits, underscores or hyphens followed with : immediately to the right of the current location
|
||||
|
@ -40,7 +43,10 @@ class KirbyTags
|
|||
return KirbyTag::parse($match[0], $data, $options)->render();
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// stay silent in production and ignore non-existing tags
|
||||
if ($debug !== true || Str::startsWith($e->getMessage(), 'Undefined tag type:') === true) {
|
||||
if (
|
||||
$debug !== true ||
|
||||
Str::startsWith($e->getMessage(), 'Undefined tag type:') === true
|
||||
) {
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
|
@ -52,6 +58,6 @@ class KirbyTags
|
|||
|
||||
return $match[0];
|
||||
}
|
||||
}, $text ?? '');
|
||||
}, $text);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue