Upgrade to 3.9.2

This commit is contained in:
Bastian Allgeier
2023-03-08 12:24:06 +01:00
parent c58864a585
commit 3b0b4feb16
44 changed files with 672 additions and 201 deletions

View File

@@ -482,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);
}

View File

@@ -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
*

View File

@@ -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');
}

View 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;
}
}

View File

@@ -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
}
}

View File

@@ -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(),
]);
}

View File

@@ -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)
*

View File

@@ -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
*

View File

@@ -56,6 +56,7 @@ class Layout extends Item
parent::__construct($params);
$this->columns = LayoutColumns::factory($params['columns'] ?? [], [
'field' => $this->field,
'parent' => $this->parent
]);

View File

@@ -41,6 +41,7 @@ class LayoutColumn extends Item
parent::__construct($params);
$this->blocks = Blocks::factory($params['blocks'] ?? [], [
'field' => $this->field,
'parent' => $this->parent
]);

View File

@@ -98,6 +98,9 @@ class Layouts extends Items
}
}
return Blocks::factory($blocks);
return Blocks::factory($blocks, [
'field' => $this->field,
'parent' => $this->parent
]);
}
}

View File

@@ -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

View File

@@ -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()]

View File

@@ -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);

View File

@@ -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
*/

View File

@@ -57,7 +57,7 @@ class Dir
string $dir,
string $target,
bool $recursive = true,
array|bool $ignore = []
array|false $ignore = []
): bool {
if (is_dir($dir) === false) {
throw new Exception('The directory "' . $dir . '" does not exist');
@@ -139,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;

View File

@@ -2,6 +2,7 @@
namespace Kirby\Image;
use Kirby\Cms\Content;
use Kirby\Exception\LogicException;
use Kirby\Filesystem\File;
use Kirby\Toolkit\Html;
@@ -114,8 +115,13 @@ class Image extends File
{
// if no alt text explicitly provided,
// try to infer from model content file
if ($alt = $this->model?->alt()) {
$attr['alt'] ??= $alt;
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()) {

View File

@@ -2,6 +2,7 @@
namespace Kirby\Option;
use Kirby\Cms\Field;
use Kirby\Cms\ModelWithContent;
use Kirby\Cms\Nest;
use Kirby\Data\Json;
@@ -108,45 +109,38 @@ class OptionsApi extends OptionsProvider
// 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);
// optionally query a substructure inside the data array
if ($this->query !== null) {
// turn data into Nest so that it can be queried
$data = Nest::create($data);
// actually apply the query and turn the result back into an array
$data = Query::factory($this->query)->resolve($data)->toArray();
}
$data = Query::factory($this->query)->resolve($data);
$options = [];
// create options by resolving text and value query strings
// for each item from the data
$options = array_map(
function ($item, $key) use ($model, $safeMode) {
// convert simple `key: value` API data
if (is_string($item) === true) {
$item = [
'key' => $key,
'value' => $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';
$safeMethod = $safeMode === true ? 'toSafeString' : 'toString';
return [
// 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])
];
},
// separately pass values and keys to have the keys available in the callback
$data,
array_keys($data)
);
$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);

View File

@@ -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);
}

View File

@@ -4,6 +4,7 @@ namespace Kirby\Toolkit;
use Closure;
use Exception;
use InvalidArgumentException;
/**
* The `A` class provides a set of handy methods
@@ -161,6 +162,33 @@ class A
return implode($separator, $value);
}
/**
* Takes an array and makes it associative by an argument.
* If the argument is a callable, it will be used to map the array.
* If it is a string, it will be used as a key to pluck from the array.
*
* <code>
* $array = [['id'=>1], ['id'=>2], ['id'=>3]];
* $keyed = A::keyBy($array, 'id');
*
* // Now you can access the array by the id
* </code>
*
* @param array $array
* @param string|callable $keyBy
* @return array
*/
public static function keyBy(array $array, string|callable $keyBy): array
{
$keys = is_callable($keyBy) ? static::map($array, $keyBy) : static::pluck($array, $keyBy);
if (count($keys) !== count($array)) {
throw new InvalidArgumentException('The "key by" argument must be a valid key or a callable');
}
return array_combine($keys, $array);
}
public const MERGE_OVERWRITE = 0;
public const MERGE_APPEND = 1;
public const MERGE_REPLACE = 2;
@@ -404,15 +432,14 @@ class A
* @param int $limit The number of elements the array should
* contain after filling it up.
* @param mixed $fill The element, which should be used to
* fill the array
* fill the array. If it's a callable, it
* will be called with the current index
* @return array The filled-up result array
*/
public static function fill(array $array, int $limit, $fill = 'placeholder'): array
{
$diff = $limit - count($array);
for ($x = 0; $x < $diff; $x++) {
$array[] = $fill;
for ($x = count($array); $x < $limit; $x++) {
$array[] = is_callable($fill) ? $fill($x) : $fill;
}
return $array;

View File

@@ -8,11 +8,11 @@ use DOMDocument;
use DOMDocumentType;
use DOMElement;
use DOMNode;
use DOMNodeList;
use DOMProcessingInstruction;
use DOMText;
use DOMXPath;
use Kirby\Cms\App;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
/**
@@ -122,20 +122,16 @@ class Dom
/**
* Returns the HTML body if one exists
*
* @return \DOMElement|null
*/
public function body()
public function body(): DOMElement|null
{
return $this->body ??= $this->query('/html/body')[0] ?? null;
}
/**
* Returns the document object
*
* @return \DOMDocument
*/
public function document()
public function document(): DOMDocument
{
return $this->doc;
}
@@ -143,9 +139,6 @@ class Dom
/**
* Extracts all URLs wrapped in a url() wrapper. E.g. for style attributes.
* @internal
*
* @param string $value
* @return array
*/
public static function extractUrls(string $value): array
{
@@ -170,12 +163,12 @@ class Dom
* Checks for allowed attributes according to the allowlist
* @internal
*
* @param \DOMAttr $attr
* @param array $options
* @return true|string If not allowed, an error message is returned
*/
public static function isAllowedAttr(DOMAttr $attr, array $options)
{
public static function isAllowedAttr(
DOMAttr $attr,
array $options
): bool|string {
$allowedTags = $options['allowedTags'];
// check if the attribute is in the list of global allowed attributes
@@ -218,12 +211,12 @@ class Dom
* Checks for allowed attributes according to the global allowlist
* @internal
*
* @param \DOMAttr $attr
* @param array $options
* @return true|string If not allowed, an error message is returned
*/
public static function isAllowedGlobalAttr(DOMAttr $attr, array $options)
{
public static function isAllowedGlobalAttr(
DOMAttr $attr,
array $options
): bool|string {
$allowedAttrs = $options['allowedAttrs'];
if ($allowedAttrs === true) {
@@ -256,12 +249,12 @@ class Dom
* Checks if the URL is acceptable for URL attributes
* @internal
*
* @param string $url
* @param array $options
* @return true|string If not allowed, an error message is returned
*/
public static function isAllowedUrl(string $url, array $options)
{
public static function isAllowedUrl(
string $url,
array $options
): bool|string {
$url = Str::lower($url);
// allow empty URL values
@@ -393,8 +386,6 @@ class Dom
* Otherwise DOMDocument won't be available and the Dom cannot
* work at all.
*
* @return bool
*
* @codeCoverageIgnore
*/
public static function isSupported(): bool
@@ -404,9 +395,6 @@ class Dom
/**
* Returns the XML or HTML markup contained in the node
*
* @param \DOMNode $node
* @return string
*/
public function innerMarkup(DOMNode $node): string
{
@@ -425,14 +413,16 @@ class Dom
* the allowed namespaces
* @internal
*
* @param array $list
* @param \DOMNode $node
* @param array $options See `Dom::sanitize()`
* @param \Closure|null Comparison callback that returns whether the expected and real name match
* @return string|false Matched name in the list or `false`
*/
public static function listContainsName(array $list, DOMNode $node, array $options, Closure|null $compare = null)
{
public static function listContainsName(
array $list,
DOMNode $node,
array $options,
Closure|null $compare = null
): string|false {
$allowedNamespaces = $options['allowedNamespaces'];
$localName = $node->localName;
$compare ??= fn ($expected, $real): bool => $expected === $real;
@@ -474,13 +464,19 @@ class Dom
}
// try if we can find an exact namespaced match
if ($namespaceUri === $node->namespaceURI && $compare($itemLocal, $localName) === true) {
if (
$namespaceUri === $node->namespaceURI &&
$compare($itemLocal, $localName) === true
) {
return $item;
}
// also try to match the fully-qualified name
// if the document doesn't define the namespace
if ($node->namespaceURI === null && $compare($item, $node->nodeName) === true) {
if (
$node->namespaceURI === null &&
$compare($item, $node->nodeName) === true
) {
return $item;
}
}
@@ -490,9 +486,6 @@ class Dom
/**
* Removes a node from the document
*
* @param \DOMNode $node
* @return void
*/
public static function remove(DOMNode $node): void
{
@@ -502,12 +495,12 @@ class Dom
/**
* Executes an XPath query in the document
*
* @param string $query
* @param \DOMNode|null $node Optional context node for relative queries
* @return \DOMNodeList|false
*/
public function query(string $query, ?DOMNode $node = null)
{
public function query(
string $query,
DOMNode|null $node = null
): DOMNodeList|false {
return (new DOMXPath($this->doc))->query($query, $node);
}
@@ -602,7 +595,6 @@ class Dom
* is exported with an XML declaration/
* full HTML markup even if the input
* didn't have them
* @return string
*/
public function toString(bool $normalize = false): string
{
@@ -623,9 +615,6 @@ class Dom
/**
* Removes a node from the document but keeps its children
* by moving them one level up
*
* @param \DOMNode $node
* @return void
*/
public static function unwrap(DOMNode $node): void
{
@@ -648,7 +637,6 @@ class Dom
* @param bool $normalize If set to `true`, the document
* is exported with full HTML markup
* even if the input didn't have it
* @return string
*/
protected function exportHtml(bool $normalize = false): string
{
@@ -688,11 +676,13 @@ class Dom
* @param bool $normalize If set to `true`, the document
* is exported with an XML declaration
* even if the input didn't have it
* @return string
*/
protected function exportXml(bool $normalize = false): string
{
if (Str::contains($this->code, '<?xml ', true) === false && $normalize === false) {
if (
Str::contains($this->code, '<?xml ', true) === false &&
$normalize === false
) {
// the input didn't contain an XML declaration;
// only return child nodes, which omits it
$result = [];
@@ -714,13 +704,14 @@ class Dom
/**
* Sanitizes an attribute
*
* @param \DOMAttr $attr
* @param array $options See `Dom::sanitize()`
* @param array $errors Array to store additional errors in by reference
* @return void
*/
protected function sanitizeAttr(DOMAttr $attr, array $options, array &$errors): void
{
protected function sanitizeAttr(
DOMAttr $attr,
array $options,
array &$errors
): void {
$element = $attr->ownerElement;
$name = $attr->nodeName;
$value = $attr->value;
@@ -762,13 +753,14 @@ class Dom
/**
* Sanitizes the doctype
*
* @param \DOMDocumentType $doctype
* @param array $options See `Dom::sanitize()`
* @param array $errors Array to store additional errors in by reference
* @return void
*/
protected function sanitizeDoctype(DOMDocumentType $doctype, array $options, array &$errors): void
{
protected function sanitizeDoctype(
DOMDocumentType $doctype,
array $options,
array &$errors
): void {
try {
$this->validateDoctype($doctype, $options);
} catch (InvalidArgumentException $e) {
@@ -780,13 +772,14 @@ class Dom
/**
* Sanitizes a single DOM element and its attribute
*
* @param \DOMElement $element
* @param array $options See `Dom::sanitize()`
* @param array $errors Array to store additional errors in by reference
* @return void
*/
protected function sanitizeElement(DOMElement $element, array $options, array &$errors): void
{
protected function sanitizeElement(
DOMElement $element,
array $options,
array &$errors
): void {
$name = $element->nodeName;
// check defined namespaces (`xmlns` attributes);
@@ -862,13 +855,14 @@ class Dom
/**
* Sanitizes a single XML processing instruction
*
* @param \DOMProcessingInstruction $pi
* @param array $options See `Dom::sanitize()`
* @param array $errors Array to store additional errors in by reference
* @return void
*/
protected function sanitizePI(DOMProcessingInstruction $pi, array $options, array &$errors): void
{
protected function sanitizePI(
DOMProcessingInstruction $pi,
array $options,
array &$errors
): void {
$name = $pi->nodeName;
// check for allow-listed processing instructions
@@ -884,15 +878,18 @@ class Dom
/**
* Validates the document type
*
* @param \DOMDocumentType $doctype
* @param array $options See `Dom::sanitize()`
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the doctype is not valid
*/
protected function validateDoctype(DOMDocumentType $doctype, array $options): void
{
if (empty($doctype->publicId) === false || empty($doctype->systemId) === false) {
protected function validateDoctype(
DOMDocumentType $doctype,
array $options
): void {
if (
empty($doctype->publicId) === false ||
empty($doctype->systemId) === false
) {
throw new InvalidArgumentException('The doctype must not reference external files');
}