Update Composer packages

This commit is contained in:
Paul Nicoué 2023-04-14 16:34:06 +02:00
parent 67c3d8b307
commit 83cb211fe6
219 changed files with 6487 additions and 4444 deletions

123
kirby/src/Template/Slot.php Normal file
View 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;
}
}

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

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

View file

@ -0,0 +1,208 @@
<?php
namespace Kirby\Template;
use Exception;
use Kirby\Cms\App;
use Kirby\Filesystem\F;
use Kirby\Toolkit\Tpl;
/**
* Represents a Kirby template and takes care
* of loading the correct file.
*
* @package Kirby Template
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class Template
{
/**
* Global template data
*/
public static array $data = [];
/**
* Default template type if no specific type is set
*/
protected string $defaultType;
/**
* The name of the template
*/
protected string $name;
/**
* Template type (html, json, etc.)
*/
protected string $type;
/**
* Creates a new template object
*/
public function __construct(string $name, string $type = 'html', string $defaultType = 'html')
{
$this->name = strtolower($name);
$this->type = $type;
$this->defaultType = $defaultType;
}
/**
* Converts the object to a simple string
* This is used in template filters for example
*/
public function __toString(): string
{
return $this->name;
}
/**
* Returns the default template type
*/
public function defaultType(): string
{
return $this->defaultType;
}
/**
* Checks if the template exists
*/
public function exists(): bool
{
if ($file = $this->file()) {
return file_exists($file);
}
return false;
}
/**
* Returns the expected template file extension
*/
public function extension(): string
{
return 'php';
}
/**
* Detects the location of the template file
* if it exists.
*/
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($root . '/' . $name . '.' . $extension, $root);
} catch (Exception) {
// ignore errors, continue searching
}
// Look for the default template provided by an extension.
$path = App::instance()->extension($store, $name);
if ($path !== null) {
return $path;
}
}
$name .= '.' . $this->type();
try {
// Try the template with type extension in the default template directory.
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($store, $name);
}
}
/**
* Checks if the template uses the default type
*/
public function hasDefaultType(): bool
{
return $this->type() === $this->defaultType();
}
/**
* Returns the template name
*/
public function name(): string
{
return $this->name;
}
/**
* Renders the template with the given template data
*/
public function render(array $data = []): string
{
// 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
*/
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
*/
public function type(): string
{
return $this->type;
}
}