Upgrade to 3.7.1

This commit is contained in:
Bastian Allgeier
2022-07-12 13:33:21 +02:00
parent 7931eb5e47
commit 1ad1eaf387
377 changed files with 63981 additions and 63824 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -19,278 +19,278 @@ use TypeError;
*/
class Component
{
/**
* Registry for all component mixins
*
* @var array
*/
public static $mixins = [];
/**
* Registry for all component mixins
*
* @var array
*/
public static $mixins = [];
/**
* Registry for all component types
*
* @var array
*/
public static $types = [];
/**
* Registry for all component types
*
* @var array
*/
public static $types = [];
/**
* An array of all passed attributes
*
* @var array
*/
protected $attrs = [];
/**
* An array of all passed attributes
*
* @var array
*/
protected $attrs = [];
/**
* An array of all computed properties
*
* @var array
*/
protected $computed = [];
/**
* An array of all computed properties
*
* @var array
*/
protected $computed = [];
/**
* An array of all registered methods
*
* @var array
*/
protected $methods = [];
/**
* An array of all registered methods
*
* @var array
*/
protected $methods = [];
/**
* An array of all component options
* from the component definition
*
* @var array
*/
protected $options = [];
/**
* An array of all component options
* from the component definition
*
* @var array
*/
protected $options = [];
/**
* An array of all resolved props
*
* @var array
*/
protected $props = [];
/**
* An array of all resolved props
*
* @var array
*/
protected $props = [];
/**
* The component type
*
* @var string
*/
protected $type;
/**
* The component type
*
* @var string
*/
protected $type;
/**
* Magic caller for defined methods and properties
*
* @param string $name
* @param array $arguments
* @return mixed
*/
public function __call(string $name, array $arguments = [])
{
if (array_key_exists($name, $this->computed) === true) {
return $this->computed[$name];
}
/**
* Magic caller for defined methods and properties
*
* @param string $name
* @param array $arguments
* @return mixed
*/
public function __call(string $name, array $arguments = [])
{
if (array_key_exists($name, $this->computed) === true) {
return $this->computed[$name];
}
if (array_key_exists($name, $this->props) === true) {
return $this->props[$name];
}
if (array_key_exists($name, $this->props) === true) {
return $this->props[$name];
}
if (array_key_exists($name, $this->methods) === true) {
return $this->methods[$name]->call($this, ...$arguments);
}
if (array_key_exists($name, $this->methods) === true) {
return $this->methods[$name]->call($this, ...$arguments);
}
return $this->$name;
}
return $this->$name;
}
/**
* Creates a new component for the given type
*
* @param string $type
* @param array $attrs
*/
public function __construct(string $type, array $attrs = [])
{
if (isset(static::$types[$type]) === false) {
throw new InvalidArgumentException('Undefined component type: ' . $type);
}
/**
* Creates a new component for the given type
*
* @param string $type
* @param array $attrs
*/
public function __construct(string $type, array $attrs = [])
{
if (isset(static::$types[$type]) === false) {
throw new InvalidArgumentException('Undefined component type: ' . $type);
}
$this->attrs = $attrs;
$this->options = $options = $this->setup($type);
$this->methods = $methods = $options['methods'] ?? [];
$this->attrs = $attrs;
$this->options = $options = $this->setup($type);
$this->methods = $methods = $options['methods'] ?? [];
foreach ($attrs as $attrName => $attrValue) {
$this->$attrName = $attrValue;
}
foreach ($attrs as $attrName => $attrValue) {
$this->$attrName = $attrValue;
}
if (isset($options['props']) === true) {
$this->applyProps($options['props']);
}
if (isset($options['props']) === true) {
$this->applyProps($options['props']);
}
if (isset($options['computed']) === true) {
$this->applyComputed($options['computed']);
}
if (isset($options['computed']) === true) {
$this->applyComputed($options['computed']);
}
$this->attrs = $attrs;
$this->methods = $methods;
$this->options = $options;
$this->type = $type;
}
$this->attrs = $attrs;
$this->methods = $methods;
$this->options = $options;
$this->type = $type;
}
/**
* Improved `var_dump` output
*
* @return array
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Improved `var_dump` output
*
* @return array
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Fallback for missing properties to return
* null instead of an error
*
* @param string $attr
* @return null
*/
public function __get(string $attr)
{
return null;
}
/**
* Fallback for missing properties to return
* null instead of an error
*
* @param string $attr
* @return null
*/
public function __get(string $attr)
{
return null;
}
/**
* A set of default options for each component.
* This can be overwritten by extended classes
* to define basic options that should always
* be applied.
*
* @return array
*/
public static function defaults(): array
{
return [];
}
/**
* A set of default options for each component.
* This can be overwritten by extended classes
* to define basic options that should always
* be applied.
*
* @return array
*/
public static function defaults(): array
{
return [];
}
/**
* Register all defined props and apply the
* passed values.
*
* @param array $props
* @return void
*/
protected function applyProps(array $props): void
{
foreach ($props as $propName => $propFunction) {
if (is_a($propFunction, 'Closure') === true) {
if (isset($this->attrs[$propName]) === true) {
try {
$this->$propName = $this->props[$propName] = $propFunction->call($this, $this->attrs[$propName]);
} catch (TypeError $e) {
throw new TypeError('Invalid value for "' . $propName . '"');
}
} else {
try {
$this->$propName = $this->props[$propName] = $propFunction->call($this);
} catch (ArgumentCountError $e) {
throw new ArgumentCountError('Please provide a value for "' . $propName . '"');
}
}
} else {
$this->$propName = $this->props[$propName] = $propFunction;
}
}
}
/**
* Register all defined props and apply the
* passed values.
*
* @param array $props
* @return void
*/
protected function applyProps(array $props): void
{
foreach ($props as $propName => $propFunction) {
if (is_a($propFunction, 'Closure') === true) {
if (isset($this->attrs[$propName]) === true) {
try {
$this->$propName = $this->props[$propName] = $propFunction->call($this, $this->attrs[$propName]);
} catch (TypeError $e) {
throw new TypeError('Invalid value for "' . $propName . '"');
}
} else {
try {
$this->$propName = $this->props[$propName] = $propFunction->call($this);
} catch (ArgumentCountError $e) {
throw new ArgumentCountError('Please provide a value for "' . $propName . '"');
}
}
} else {
$this->$propName = $this->props[$propName] = $propFunction;
}
}
}
/**
* Register all computed properties and calculate their values.
* This must happen after all props are registered.
*
* @param array $computed
* @return void
*/
protected function applyComputed(array $computed): void
{
foreach ($computed as $computedName => $computedFunction) {
if (is_a($computedFunction, 'Closure') === true) {
$this->$computedName = $this->computed[$computedName] = $computedFunction->call($this);
}
}
}
/**
* Register all computed properties and calculate their values.
* This must happen after all props are registered.
*
* @param array $computed
* @return void
*/
protected function applyComputed(array $computed): void
{
foreach ($computed as $computedName => $computedFunction) {
if (is_a($computedFunction, 'Closure') === true) {
$this->$computedName = $this->computed[$computedName] = $computedFunction->call($this);
}
}
}
/**
* Load a component definition by type
*
* @param string $type
* @return array
*/
public static function load(string $type): array
{
$definition = static::$types[$type];
/**
* Load a component definition by type
*
* @param string $type
* @return array
*/
public static function load(string $type): array
{
$definition = static::$types[$type];
// load definitions from string
if (is_string($definition) === true) {
if (is_file($definition) !== true) {
throw new Exception('Component definition ' . $definition . ' does not exist');
}
// load definitions from string
if (is_string($definition) === true) {
if (is_file($definition) !== true) {
throw new Exception('Component definition ' . $definition . ' does not exist');
}
static::$types[$type] = $definition = F::load($definition);
}
static::$types[$type] = $definition = F::load($definition);
}
return $definition;
}
return $definition;
}
/**
* Loads all options from the component definition
* mixes in the defaults from the defaults method and
* then injects all additional mixins, defined in the
* component options.
*
* @param string $type
* @return array
*/
public static function setup(string $type): array
{
// load component definition
$definition = static::load($type);
/**
* Loads all options from the component definition
* mixes in the defaults from the defaults method and
* then injects all additional mixins, defined in the
* component options.
*
* @param string $type
* @return array
*/
public static function setup(string $type): array
{
// load component definition
$definition = static::load($type);
if (isset($definition['extends']) === true) {
// extend other definitions
$options = array_replace_recursive(static::defaults(), static::load($definition['extends']), $definition);
} else {
// inject defaults
$options = array_replace_recursive(static::defaults(), $definition);
}
if (isset($definition['extends']) === true) {
// extend other definitions
$options = array_replace_recursive(static::defaults(), static::load($definition['extends']), $definition);
} else {
// inject defaults
$options = array_replace_recursive(static::defaults(), $definition);
}
// inject mixins
if (isset($options['mixins']) === true) {
foreach ($options['mixins'] as $mixin) {
if (isset(static::$mixins[$mixin]) === true) {
if (is_string(static::$mixins[$mixin]) === true) {
// resolve a path to a mixin on demand
static::$mixins[$mixin] = include static::$mixins[$mixin];
}
// inject mixins
if (isset($options['mixins']) === true) {
foreach ($options['mixins'] as $mixin) {
if (isset(static::$mixins[$mixin]) === true) {
if (is_string(static::$mixins[$mixin]) === true) {
// resolve a path to a mixin on demand
static::$mixins[$mixin] = include static::$mixins[$mixin];
}
$options = array_replace_recursive(static::$mixins[$mixin], $options);
}
}
}
$options = array_replace_recursive(static::$mixins[$mixin], $options);
}
}
}
return $options;
}
return $options;
}
/**
* Converts all props and computed props to an array
*
* @return array
*/
public function toArray(): array
{
if (is_a($this->options['toArray'] ?? null, 'Closure') === true) {
return $this->options['toArray']->call($this);
}
/**
* Converts all props and computed props to an array
*
* @return array
*/
public function toArray(): array
{
if (is_a($this->options['toArray'] ?? null, 'Closure') === true) {
return $this->options['toArray']->call($this);
}
$array = array_merge($this->attrs, $this->props, $this->computed);
$array = array_merge($this->attrs, $this->props, $this->computed);
ksort($array);
ksort($array);
return $array;
}
return $array;
}
}

View File

@@ -14,8 +14,8 @@ namespace Kirby\Toolkit;
*/
class Config extends Silo
{
/**
* @var array
*/
public static $data = [];
/**
* @var array
*/
public static $data = [];
}

View File

@@ -19,49 +19,49 @@ use ReflectionFunction;
*/
class Controller
{
protected $function;
protected $function;
public function __construct(Closure $function)
{
$this->function = $function;
}
public function __construct(Closure $function)
{
$this->function = $function;
}
public function arguments(array $data = []): array
{
$info = new ReflectionFunction($this->function);
$args = [];
public function arguments(array $data = []): array
{
$info = new ReflectionFunction($this->function);
$args = [];
foreach ($info->getParameters() as $parameter) {
$name = $parameter->getName();
$args[] = $data[$name] ?? null;
}
foreach ($info->getParameters() as $parameter) {
$name = $parameter->getName();
$args[] = $data[$name] ?? null;
}
return $args;
}
return $args;
}
public function call($bind = null, $data = [])
{
$args = $this->arguments($data);
public function call($bind = null, $data = [])
{
$args = $this->arguments($data);
if ($bind === null) {
return call_user_func($this->function, ...$args);
}
if ($bind === null) {
return call_user_func($this->function, ...$args);
}
return $this->function->call($bind, ...$args);
}
return $this->function->call($bind, ...$args);
}
public static function load(string $file)
{
if (is_file($file) === false) {
return null;
}
public static function load(string $file)
{
if (is_file($file) === false) {
return null;
}
$function = F::load($file);
$function = F::load($file);
if (is_a($function, 'Closure') === false) {
return null;
}
if (is_a($function, 'Closure') === false) {
return null;
}
return new static($function);
}
return new static($function);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -23,140 +23,140 @@ use Laminas\Escaper\Escaper;
*/
class Escape
{
/**
* The internal singleton escaper instance
*
* @var \Laminas\Escaper\Escaper
*/
protected static $escaper;
/**
* The internal singleton escaper instance
*
* @var \Laminas\Escaper\Escaper
*/
protected static $escaper;
/**
* Escape common HTML attributes data
*
* This can be used to put untrusted data into typical attribute values
* like width, name, value, etc.
*
* This should not be used for complex attributes like href, src, style,
* or any of the event handlers like onmouseover.
* Use esc($string, 'js') for event handler attributes, esc($string, 'url')
* for src attributes and esc($string, 'css') for style attributes.
*
* <div attr=...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...>content</div>
* <div attr='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'>content</div>
* <div attr="...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">content</div>
*
* @param string $string
* @return string
*/
public static function attr($string)
{
return static::escaper()->escapeHtmlAttr($string);
}
/**
* Escape common HTML attributes data
*
* This can be used to put untrusted data into typical attribute values
* like width, name, value, etc.
*
* This should not be used for complex attributes like href, src, style,
* or any of the event handlers like onmouseover.
* Use esc($string, 'js') for event handler attributes, esc($string, 'url')
* for src attributes and esc($string, 'css') for style attributes.
*
* <div attr=...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...>content</div>
* <div attr='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'>content</div>
* <div attr="...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">content</div>
*
* @param string $string
* @return string
*/
public static function attr($string)
{
return static::escaper()->escapeHtmlAttr($string);
}
/**
* Escape HTML style property values
*
* This can be used to put untrusted data into a stylesheet or a style tag.
*
* Stay away from putting untrusted data into complex properties like url,
* behavior, and custom (-moz-binding). You should also not put untrusted data
* into IEs expression property value which allows JavaScript.
*
* <style>selector { property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...; } </style>
* <style>selector { property : "...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE..."; } </style>
* <span style="property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">text</span>
*
* @param string $string
* @return string
*/
public static function css($string)
{
return static::escaper()->escapeCss($string);
}
/**
* Escape HTML style property values
*
* This can be used to put untrusted data into a stylesheet or a style tag.
*
* Stay away from putting untrusted data into complex properties like url,
* behavior, and custom (-moz-binding). You should also not put untrusted data
* into IEs expression property value which allows JavaScript.
*
* <style>selector { property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...; } </style>
* <style>selector { property : "...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE..."; } </style>
* <span style="property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">text</span>
*
* @param string $string
* @return string
*/
public static function css($string)
{
return static::escaper()->escapeCss($string);
}
/**
* Get the escaper instance (and create if needed)
*
* @return \Laminas\Escaper\Escaper
*/
protected static function escaper()
{
return static::$escaper ??= new Escaper('utf-8');
}
/**
* Get the escaper instance (and create if needed)
*
* @return \Laminas\Escaper\Escaper
*/
protected static function escaper()
{
return static::$escaper ??= new Escaper('utf-8');
}
/**
* Escape HTML element content
*
* This can be used to put untrusted data directly into the HTML body somewhere.
* This includes inside normal tags like div, p, b, td, etc.
*
* Escapes &, <, >, ", and ' with HTML entity encoding to prevent switching
* into any execution context, such as script, style, or event handlers.
*
* <body>...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...</body>
* <div>...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...</div>
*
* @param string $string
* @return string
*/
public static function html($string)
{
return static::escaper()->escapeHtml($string);
}
/**
* Escape HTML element content
*
* This can be used to put untrusted data directly into the HTML body somewhere.
* This includes inside normal tags like div, p, b, td, etc.
*
* Escapes &, <, >, ", and ' with HTML entity encoding to prevent switching
* into any execution context, such as script, style, or event handlers.
*
* <body>...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...</body>
* <div>...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...</div>
*
* @param string $string
* @return string
*/
public static function html($string)
{
return static::escaper()->escapeHtml($string);
}
/**
* Escape JavaScript data values
*
* This can be used to put dynamically generated JavaScript code
* into both script blocks and event-handler attributes.
*
* <script>alert('...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...')</script>
* <script>x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'</script>
* <div onmouseover="x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'"</div>
*
* @param string $string
* @return string
*/
public static function js($string)
{
return static::escaper()->escapeJs($string);
}
/**
* Escape JavaScript data values
*
* This can be used to put dynamically generated JavaScript code
* into both script blocks and event-handler attributes.
*
* <script>alert('...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...')</script>
* <script>x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'</script>
* <div onmouseover="x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'"</div>
*
* @param string $string
* @return string
*/
public static function js($string)
{
return static::escaper()->escapeJs($string);
}
/**
* Escape URL parameter values
*
* This can be used to put untrusted data into HTTP GET parameter values.
* This should not be used to escape an entire URI.
*
* <a href="http://www.somesite.com?test=...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">link</a>
*
* @param string $string
* @return string
*/
public static function url($string)
{
return rawurlencode($string);
}
/**
* Escape URL parameter values
*
* This can be used to put untrusted data into HTTP GET parameter values.
* This should not be used to escape an entire URI.
*
* <a href="http://www.somesite.com?test=...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">link</a>
*
* @param string $string
* @return string
*/
public static function url($string)
{
return rawurlencode($string);
}
/**
* Escape XML element content
*
* Removes offending characters that could be wrongfully interpreted as XML markup.
*
* The following characters are reserved in XML and will be replaced with their
* corresponding XML entities:
*
* ' is replaced with &apos;
* " is replaced with &quot;
* & is replaced with &amp;
* < is replaced with &lt;
* > is replaced with &gt;
*
* @param string $string
* @return string
*/
public static function xml($string)
{
return htmlspecialchars($string, ENT_QUOTES | ENT_XML1, 'UTF-8');
}
/**
* Escape XML element content
*
* Removes offending characters that could be wrongfully interpreted as XML markup.
*
* The following characters are reserved in XML and will be replaced with their
* corresponding XML entities:
*
* ' is replaced with &apos;
* " is replaced with &quot;
* & is replaced with &amp;
* < is replaced with &lt;
* > is replaced with &gt;
*
* @param string $string
* @return string
*/
public static function xml($string)
{
return htmlspecialchars($string, ENT_QUOTES | ENT_XML1, 'UTF-8');
}
}

View File

@@ -14,23 +14,23 @@ namespace Kirby\Toolkit;
*/
abstract class Facade
{
/**
* Returns the instance that should be
* available statically
*
* @return mixed
*/
abstract public static function instance();
/**
* Returns the instance that should be
* available statically
*
* @return mixed
*/
abstract public static function instance();
/**
* Proxy for all public instance calls
*
* @param string $method
* @param array $args
* @return mixed
*/
public static function __callStatic(string $method, array $args = null)
{
return static::instance()->$method(...$args);
}
/**
* Proxy for all public instance calls
*
* @param string $method
* @param array $args
* @return mixed
*/
public static function __callStatic(string $method, array $args = null)
{
return static::instance()->$method(...$args);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,280 +16,293 @@ use NumberFormatter;
*/
class I18n
{
/**
* Custom loader function
*
* @var Closure
*/
public static $load = null;
/**
* Custom loader function
*
* @var Closure
*/
public static $load = null;
/**
* Current locale
*
* @var string|\Closure
*/
public static $locale = 'en';
/**
* Current locale
*
* @var string|\Closure
*/
public static $locale = 'en';
/**
* All registered translations
*
* @var array
*/
public static $translations = [];
/**
* All registered translations
*
* @var array
*/
public static $translations = [];
/**
* The fallback locale or a
* list of fallback locales
*
* @var string|array|\Closure
*/
public static $fallback = ['en'];
/**
* The fallback locale or a
* list of fallback locales
*
* @var string|array|\Closure
*/
public static $fallback = ['en'];
/**
* Cache of `NumberFormatter` objects by locale
*
* @var array
*/
protected static $decimalsFormatters = [];
/**
* Cache of `NumberFormatter` objects by locale
*
* @var array
*/
protected static $decimalsFormatters = [];
/**
* Returns the list of fallback locales
*
* @return array
*/
public static function fallbacks(): array
{
if (
is_array(static::$fallback) === true ||
is_string(static::$fallback) === true
) {
return A::wrap(static::$fallback);
}
/**
* Returns the list of fallback locales
*
* @return array
*/
public static function fallbacks(): array
{
if (
is_array(static::$fallback) === true ||
is_string(static::$fallback) === true
) {
return A::wrap(static::$fallback);
}
if (is_callable(static::$fallback) === true) {
return static::$fallback = A::wrap((static::$fallback)());
}
if (is_callable(static::$fallback) === true) {
return static::$fallback = A::wrap((static::$fallback)());
}
return static::$fallback = ['en'];
}
return static::$fallback = ['en'];
}
/**
* Returns singular or plural
* depending on the given number
*
* @param int $count
* @param bool $none If true, 'none' will be returned if the count is 0
* @return string
*/
public static function form(int $count, bool $none = false): string
{
if ($none === true && $count === 0) {
return 'none';
}
/**
* Returns singular or plural
* depending on the given number
*
* @param int $count
* @param bool $none If true, 'none' will be returned if the count is 0
* @return string
*/
public static function form(int $count, bool $none = false): string
{
if ($none === true && $count === 0) {
return 'none';
}
return $count === 1 ? 'singular' : 'plural';
}
return $count === 1 ? 'singular' : 'plural';
}
/**
* Formats a number
*
* @param int|float $number
* @param string $locale
* @return string
*/
public static function formatNumber($number, string $locale = null): string
{
$locale ??= static::locale();
/**
* Formats a number
*
* @param int|float $number
* @param string $locale
* @return string
*/
public static function formatNumber($number, string $locale = null): string
{
$locale ??= static::locale();
$formatter = static::decimalNumberFormatter($locale);
if ($formatter !== null) {
$number = $formatter->format($number);
}
$formatter = static::decimalNumberFormatter($locale);
if ($formatter !== null) {
$number = $formatter->format($number);
}
return (string)$number;
}
return (string)$number;
}
/**
* Returns the locale code
*
* @return string
*/
public static function locale(): string
{
if (is_string(static::$locale) === true) {
return static::$locale;
}
/**
* Returns the locale code
*
* @return string
*/
public static function locale(): string
{
if (is_string(static::$locale) === true) {
return static::$locale;
}
if (is_callable(static::$locale) === true) {
return static::$locale = (static::$locale)();
}
if (is_callable(static::$locale) === true) {
return static::$locale = (static::$locale)();
}
return static::$locale = 'en';
}
return static::$locale = 'en';
}
/**
* Translates a given message
* according to the currently set locale
*
* @param string|array $key
* @param string|array|null $fallback
* @param string|null $locale
* @return string|array|null
*/
public static function translate($key, $fallback = null, string $locale = null)
{
$locale ??= static::locale();
/**
* Translates a given message
* according to the currently set locale
*
* @param string|array $key
* @param string|array|null $fallback
* @param string|null $locale
* @return string|array|null
*/
public static function translate($key, $fallback = null, string $locale = null)
{
$locale ??= static::locale();
if (is_array($key) === true) {
if (isset($key[$locale])) {
return $key[$locale];
}
if (is_array($fallback)) {
return $fallback[$locale] ?? $fallback['en'] ?? reset($fallback);
}
return $fallback;
}
if (is_array($key) === true) {
// try to use actual locale
if (isset($key[$locale])) {
return $key[$locale];
}
// try to use language code, e.g. `es` when locale is `es_ES`
$lang = Str::before($locale, '_');
if (isset($key[$lang])) {
return $key[$lang];
}
// use fallback
if (is_array($fallback)) {
return $fallback[$locale] ?? $fallback['en'] ?? reset($fallback);
}
return $fallback;
}
if ($translation = static::translation($locale)[$key] ?? null) {
return $translation;
}
if ($translation = static::translation($locale)[$key] ?? null) {
return $translation;
}
if ($fallback !== null) {
return $fallback;
}
if ($fallback !== null) {
return $fallback;
}
foreach (static::fallbacks() as $fallback) {
// skip locales we have already tried
if ($locale === $fallback) {
continue;
}
foreach (static::fallbacks() as $fallback) {
// skip locales we have already tried
if ($locale === $fallback) {
continue;
}
if ($translation = static::translation($fallback)[$key] ?? null) {
return $translation;
}
}
if ($translation = static::translation($fallback)[$key] ?? null) {
return $translation;
}
}
return null;
}
return null;
}
/**
* Translate by key and then replace
* placeholders in the text
*
* @param string $key
* @param string|array|null $fallback
* @param array|null $replace
* @param string|null $locale
* @return string
*/
public static function template(string $key, $fallback = null, ?array $replace = null, ?string $locale = null): string
{
if (is_array($fallback) === true) {
$replace = $fallback;
$fallback = null;
$locale = null;
}
/**
* Translate by key and then replace
* placeholders in the text
*
* @param string $key
* @param string|array|null $fallback
* @param array|null $replace
* @param string|null $locale
* @return string
*/
public static function template(string $key, $fallback = null, ?array $replace = null, ?string $locale = null): string
{
if (is_array($fallback) === true) {
$replace = $fallback;
$fallback = null;
$locale = null;
}
$template = static::translate($key, $fallback, $locale);
return Str::template($template, $replace, [
'fallback' => '-',
'start' => '{',
'end' => '}'
]);
}
$template = static::translate($key, $fallback, $locale);
return Str::template($template, $replace, [
'fallback' => '-',
'start' => '{',
'end' => '}'
]);
}
/**
* Returns the current or any other translation
* by locale. If the translation does not exist
* yet, the loader will try to load it, if defined.
*
* @param string|null $locale
* @return array
*/
public static function translation(string $locale = null): array
{
$locale ??= static::locale();
/**
* Returns the current or any other translation
* by locale. If the translation does not exist
* yet, the loader will try to load it, if defined.
*
* @param string|null $locale
* @return array
*/
public static function translation(string $locale = null): array
{
$locale ??= static::locale();
if (isset(static::$translations[$locale]) === true) {
return static::$translations[$locale];
}
if (isset(static::$translations[$locale]) === true) {
return static::$translations[$locale];
}
if (is_a(static::$load, 'Closure') === true) {
return static::$translations[$locale] = (static::$load)($locale);
}
if (is_a(static::$load, 'Closure') === true) {
return static::$translations[$locale] = (static::$load)($locale);
}
return static::$translations[$locale] = [];
}
// try to use language code, e.g. `es` when locale is `es_ES`
$lang = Str::before($locale, '_');
if (isset(static::$translations[$lang]) === true) {
return static::$translations[$lang];
}
/**
* Returns all loaded or defined translations
*
* @return array
*/
public static function translations(): array
{
return static::$translations;
}
return static::$translations[$locale] = [];
}
/**
* Returns (and creates) a decimal number formatter for a given locale
*
* @return \NumberFormatter|null
*/
protected static function decimalNumberFormatter(string $locale): ?NumberFormatter
{
if (isset(static::$decimalsFormatters[$locale])) {
return static::$decimalsFormatters[$locale];
}
/**
* Returns all loaded or defined translations
*
* @return array
*/
public static function translations(): array
{
return static::$translations;
}
if (extension_loaded('intl') !== true || class_exists('NumberFormatter') !== true) {
return null; // @codeCoverageIgnore
}
/**
* Returns (and creates) a decimal number formatter for a given locale
*
* @return \NumberFormatter|null
*/
protected static function decimalNumberFormatter(string $locale): ?NumberFormatter
{
if (isset(static::$decimalsFormatters[$locale])) {
return static::$decimalsFormatters[$locale];
}
return static::$decimalsFormatters[$locale] = new NumberFormatter($locale, NumberFormatter::DECIMAL);
}
if (extension_loaded('intl') !== true || class_exists('NumberFormatter') !== true) {
return null; // @codeCoverageIgnore
}
/**
* Translates amounts
*
* Translation definition options:
* - Translation is a simple string: `{{ count }}` gets replaced in the template
* - Translation is an array with a value for each count: Chooses the correct template and
* replaces `{{ count }}` in the template; if no specific template for the input count is
* defined, the template that is defined last in the translation array is used
* - Translation is a callback with a `$count` argument: Returns the callback return value
*
* @param string $key
* @param int $count
* @param string|null $locale
* @param bool $formatNumber If set to `false`, the count is not formatted
* @return mixed
*/
public static function translateCount(string $key, int $count, string $locale = null, bool $formatNumber = true)
{
$locale ??= static::locale();
$translation = static::translate($key, null, $locale);
return static::$decimalsFormatters[$locale] = new NumberFormatter($locale, NumberFormatter::DECIMAL);
}
if ($translation === null) {
return null;
}
/**
* Translates amounts
*
* Translation definition options:
* - Translation is a simple string: `{{ count }}` gets replaced in the template
* - Translation is an array with a value for each count: Chooses the correct template and
* replaces `{{ count }}` in the template; if no specific template for the input count is
* defined, the template that is defined last in the translation array is used
* - Translation is a callback with a `$count` argument: Returns the callback return value
*
* @param string $key
* @param int $count
* @param string|null $locale
* @param bool $formatNumber If set to `false`, the count is not formatted
* @return mixed
*/
public static function translateCount(string $key, int $count, string $locale = null, bool $formatNumber = true)
{
$locale ??= static::locale();
$translation = static::translate($key, null, $locale);
if (is_a($translation, 'Closure') === true) {
return $translation($count);
}
if ($translation === null) {
return null;
}
if (is_string($translation) === true) {
$message = $translation;
} elseif (isset($translation[$count]) === true) {
$message = $translation[$count];
} else {
$message = end($translation);
}
if (is_a($translation, 'Closure') === true) {
return $translation($count);
}
if ($formatNumber === true) {
$count = static::formatNumber($count, $locale);
}
if (is_string($translation) === true) {
$message = $translation;
} elseif (isset($translation[$count]) === true) {
$message = $translation[$count];
} else {
$message = end($translation);
}
return str_replace('{{ count }}', $count, $message);
}
if ($formatNumber === true) {
$count = static::formatNumber($count, $locale);
}
return str_replace('{{ count }}', $count, $message);
}
}

View File

@@ -18,164 +18,164 @@ use IteratorAggregate;
*/
class Iterator implements IteratorAggregate
{
/**
* The data array
*
* @var array
*/
public $data = [];
/**
* The data array
*
* @var array
*/
public $data = [];
/**
* Constructor
*
* @param array $data
*/
public function __construct(array $data = [])
{
$this->data = $data;
}
/**
* Constructor
*
* @param array $data
*/
public function __construct(array $data = [])
{
$this->data = $data;
}
/**
* Get an iterator for the items.
*
* @return \ArrayIterator
*/
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->data);
}
/**
* Get an iterator for the items.
*
* @return \ArrayIterator
*/
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->data);
}
/**
* Returns the current key
*
* @return string
*/
public function key()
{
return key($this->data);
}
/**
* Returns the current key
*
* @return string
*/
public function key()
{
return key($this->data);
}
/**
* Returns an array of all keys
*
* @return array
*/
public function keys(): array
{
return array_keys($this->data);
}
/**
* Returns an array of all keys
*
* @return array
*/
public function keys(): array
{
return array_keys($this->data);
}
/**
* Returns the current element
*
* @return mixed
*/
public function current()
{
return current($this->data);
}
/**
* Returns the current element
*
* @return mixed
*/
public function current()
{
return current($this->data);
}
/**
* Moves the cursor to the previous element
* and returns it
*
* @return mixed
*/
public function prev()
{
return prev($this->data);
}
/**
* Moves the cursor to the previous element
* and returns it
*
* @return mixed
*/
public function prev()
{
return prev($this->data);
}
/**
* Moves the cursor to the next element
* and returns it
*
* @return mixed
*/
public function next()
{
return next($this->data);
}
/**
* Moves the cursor to the next element
* and returns it
*
* @return mixed
*/
public function next()
{
return next($this->data);
}
/**
* Moves the cursor to the first element
*/
public function rewind()
{
reset($this->data);
}
/**
* Moves the cursor to the first element
*/
public function rewind()
{
reset($this->data);
}
/**
* Checks if the current element is valid
*
* @return bool
*/
public function valid(): bool
{
return $this->current() !== false;
}
/**
* Checks if the current element is valid
*
* @return bool
*/
public function valid(): bool
{
return $this->current() !== false;
}
/**
* Counts all elements
*
* @return int
*/
public function count(): int
{
return count($this->data);
}
/**
* Counts all elements
*
* @return int
*/
public function count(): int
{
return count($this->data);
}
/**
* Tries to find the index number for the given element
*
* @param mixed $needle the element to search for
* @return int|false the index (int) of the element or false
*/
public function indexOf($needle)
{
return array_search($needle, array_values($this->data));
}
/**
* Tries to find the index number for the given element
*
* @param mixed $needle the element to search for
* @return int|false the index (int) of the element or false
*/
public function indexOf($needle)
{
return array_search($needle, array_values($this->data));
}
/**
* Tries to find the key for the given element
*
* @param mixed $needle the element to search for
* @return string|false the name of the key or false
*/
public function keyOf($needle)
{
return array_search($needle, $this->data);
}
/**
* Tries to find the key for the given element
*
* @param mixed $needle the element to search for
* @return string|false the name of the key or false
*/
public function keyOf($needle)
{
return array_search($needle, $this->data);
}
/**
* Checks by key if an element is included
*
* @param mixed $key
* @return bool
*/
public function has($key): bool
{
return isset($this->data[$key]);
}
/**
* Checks by key if an element is included
*
* @param mixed $key
* @return bool
*/
public function has($key): bool
{
return isset($this->data[$key]);
}
/**
* Checks if the current key is set
*
* @param mixed $key the key to check
* @return bool
*/
public function __isset($key): bool
{
return $this->has($key);
}
/**
* Checks if the current key is set
*
* @param mixed $key the key to check
* @return bool
*/
public function __isset($key): bool
{
return $this->has($key);
}
/**
* Simplified var_dump output
*
* @return array
*/
public function __debugInfo(): array
{
return $this->data;
}
/**
* Simplified var_dump output
*
* @return array
*/
public function __debugInfo(): array
{
return $this->data;
}
}

View File

@@ -17,167 +17,167 @@ use Kirby\Exception\InvalidArgumentException;
*/
class Locale
{
/**
* List of all locale constants supported by PHP
*/
public const LOCALE_CONSTANTS = [
'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY',
'LC_NUMERIC', 'LC_TIME', 'LC_MESSAGES'
];
/**
* List of all locale constants supported by PHP
*/
public const LOCALE_CONSTANTS = [
'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY',
'LC_NUMERIC', 'LC_TIME', 'LC_MESSAGES'
];
/**
* Converts a normalized locale array to an array with the
* locale constants replaced with their string representations
*
* @param array $locale
* @return array
*/
public static function export(array $locale): array
{
$constants = static::supportedConstants(true);
/**
* Converts a normalized locale array to an array with the
* locale constants replaced with their string representations
*
* @param array $locale
* @return array
*/
public static function export(array $locale): array
{
$constants = static::supportedConstants(true);
// replace the keys in the locale data array with the locale names
$return = [];
foreach ($locale as $key => $value) {
if (isset($constants[$key]) === true) {
// the key is a valid constant,
// replace it with its string representation
$return[$constants[$key]] = $value;
} else {
// not found, keep it as-is
$return[$key] = $value;
}
}
// replace the keys in the locale data array with the locale names
$return = [];
foreach ($locale as $key => $value) {
if (isset($constants[$key]) === true) {
// the key is a valid constant,
// replace it with its string representation
$return[$constants[$key]] = $value;
} else {
// not found, keep it as-is
$return[$key] = $value;
}
}
return $return;
}
return $return;
}
/**
* Returns the current locale value for
* a specified or for all locale categories
* @since 3.5.6
*
* @param int|string $category Locale category constant or constant name
* @return array|string Associative array if `LC_ALL` was passed (default), otherwise string
*
* @throws \Kirby\Exception\Exception If the locale cannot be determined
* @throws \Kirby\Exception\InvalidArgumentException If the provided locale category is invalid
*/
public static function get($category = LC_ALL)
{
$normalizedCategory = static::normalizeConstant($category);
/**
* Returns the current locale value for
* a specified or for all locale categories
* @since 3.5.6
*
* @param int|string $category Locale category constant or constant name
* @return array|string Associative array if `LC_ALL` was passed (default), otherwise string
*
* @throws \Kirby\Exception\Exception If the locale cannot be determined
* @throws \Kirby\Exception\InvalidArgumentException If the provided locale category is invalid
*/
public static function get($category = LC_ALL)
{
$normalizedCategory = static::normalizeConstant($category);
if (is_int($normalizedCategory) !== true) {
throw new InvalidArgumentException('Invalid locale category "' . $category . '"');
}
if (is_int($normalizedCategory) !== true) {
throw new InvalidArgumentException('Invalid locale category "' . $category . '"');
}
if ($normalizedCategory !== LC_ALL) {
// `setlocale(..., 0)` actually *gets* the locale
$locale = setlocale($normalizedCategory, 0);
if ($normalizedCategory !== LC_ALL) {
// `setlocale(..., 0)` actually *gets* the locale
$locale = setlocale($normalizedCategory, 0);
if (is_string($locale) !== true) {
throw new Exception('Could not determine locale for category "' . $category . '"');
}
if (is_string($locale) !== true) {
throw new Exception('Could not determine locale for category "' . $category . '"');
}
return $locale;
}
return $locale;
}
// no specific `$category` was passed, make a list of all locales
$array = [];
foreach (static::supportedConstants() as $constant => $name) {
// `setlocale(..., 0)` actually *gets* the locale
$array[$constant] = setlocale($constant, '0');
}
// no specific `$category` was passed, make a list of all locales
$array = [];
foreach (static::supportedConstants() as $constant => $name) {
// `setlocale(..., 0)` actually *gets* the locale
$array[$constant] = setlocale($constant, '0');
}
// if all values are the same, we can use `LC_ALL`
// instead of a long array with all constants
if (count(array_unique($array)) === 1) {
return [
LC_ALL => array_shift($array)
];
}
// if all values are the same, we can use `LC_ALL`
// instead of a long array with all constants
if (count(array_unique($array)) === 1) {
return [
LC_ALL => array_shift($array)
];
}
return $array;
}
return $array;
}
/**
* Converts a locale string or an array with constant or
* string keys to a normalized constant => value array
*
* @param array|string $locale
* @return array
*/
public static function normalize($locale): array
{
if (is_array($locale)) {
// replace string constant keys with the constant values
$convertedLocale = [];
foreach ($locale as $key => $value) {
$convertedLocale[static::normalizeConstant($key)] = $value;
}
/**
* Converts a locale string or an array with constant or
* string keys to a normalized constant => value array
*
* @param array|string $locale
* @return array
*/
public static function normalize($locale): array
{
if (is_array($locale)) {
// replace string constant keys with the constant values
$convertedLocale = [];
foreach ($locale as $key => $value) {
$convertedLocale[static::normalizeConstant($key)] = $value;
}
return $convertedLocale;
} elseif (is_string($locale)) {
return [LC_ALL => $locale];
} else {
throw new InvalidArgumentException('Locale must be string or array');
}
}
return $convertedLocale;
} elseif (is_string($locale)) {
return [LC_ALL => $locale];
} else {
throw new InvalidArgumentException('Locale must be string or array');
}
}
/**
* Sets the PHP locale with a locale string or
* an array with constant or string keys
*
* @param array|string $locale
* @return void
*/
public static function set($locale): void
{
$locale = static::normalize($locale);
/**
* Sets the PHP locale with a locale string or
* an array with constant or string keys
*
* @param array|string $locale
* @return void
*/
public static function set($locale): void
{
$locale = static::normalize($locale);
foreach ($locale as $key => $value) {
setlocale($key, $value);
}
}
foreach ($locale as $key => $value) {
setlocale($key, $value);
}
}
/**
* Tries to convert an `LC_*` constant name
* to its constant value
*
* @param int|string $constant
* @return int|string
*/
protected static function normalizeConstant($constant)
{
if (is_string($constant) === true && Str::startsWith($constant, 'LC_') === true) {
return constant($constant);
}
/**
* Tries to convert an `LC_*` constant name
* to its constant value
*
* @param int|string $constant
* @return int|string
*/
protected static function normalizeConstant($constant)
{
if (is_string($constant) === true && Str::startsWith($constant, 'LC_') === true) {
return constant($constant);
}
// already an int or we cannot convert it safely
return $constant;
}
// already an int or we cannot convert it safely
return $constant;
}
/**
* Builds an associative array with the locales
* that are actually supported on this system
*
* @param bool $withAll If set to `true`, `LC_ALL` is returned as well
* @return array
*/
protected static function supportedConstants(bool $withAll = false): array
{
$names = static::LOCALE_CONSTANTS;
if ($withAll === true) {
array_unshift($names, 'LC_ALL');
}
/**
* Builds an associative array with the locales
* that are actually supported on this system
*
* @param bool $withAll If set to `true`, `LC_ALL` is returned as well
* @return array
*/
protected static function supportedConstants(bool $withAll = false): array
{
$names = static::LOCALE_CONSTANTS;
if ($withAll === true) {
array_unshift($names, 'LC_ALL');
}
$constants = [];
foreach ($names as $name) {
if (defined($name) === true) {
$constants[constant($name)] = $name;
}
}
$constants = [];
foreach ($names as $name) {
if (defined($name) === true) {
$constants[constant($name)] = $name;
}
}
return $constants;
}
return $constants;
}
}

View File

@@ -17,108 +17,108 @@ use stdClass;
*/
class Obj extends stdClass
{
/**
* Constructor
*
* @param array $data
*/
public function __construct(array $data = [])
{
foreach ($data as $key => $val) {
$this->$key = $val;
}
}
/**
* Constructor
*
* @param array $data
*/
public function __construct(array $data = [])
{
foreach ($data as $key => $val) {
$this->$key = $val;
}
}
/**
* Magic getter
*
* @param string $property
* @param array $arguments
* @return mixed
*/
public function __call(string $property, array $arguments)
{
return $this->$property ?? null;
}
/**
* Magic getter
*
* @param string $property
* @param array $arguments
* @return mixed
*/
public function __call(string $property, array $arguments)
{
return $this->$property ?? null;
}
/**
* Improved `var_dump` output
*
* @return array
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Improved `var_dump` output
*
* @return array
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Magic property getter
*
* @param string $property
* @return mixed
*/
public function __get(string $property)
{
return null;
}
/**
* Magic property getter
*
* @param string $property
* @return mixed
*/
public function __get(string $property)
{
return null;
}
/**
* Gets one or multiple properties of the object
*
* @param string|array $property
* @param mixed $fallback If multiple properties are requested:
* Associative array of fallback values per key
* @return mixed
*/
public function get($property, $fallback = null)
{
if (is_array($property)) {
if ($fallback === null) {
$fallback = [];
}
/**
* Gets one or multiple properties of the object
*
* @param string|array $property
* @param mixed $fallback If multiple properties are requested:
* Associative array of fallback values per key
* @return mixed
*/
public function get($property, $fallback = null)
{
if (is_array($property)) {
if ($fallback === null) {
$fallback = [];
}
if (!is_array($fallback)) {
throw new InvalidArgumentException('The fallback value must be an array when getting multiple properties');
}
if (!is_array($fallback)) {
throw new InvalidArgumentException('The fallback value must be an array when getting multiple properties');
}
$result = [];
foreach ($property as $key) {
$result[$key] = $this->$key ?? $fallback[$key] ?? null;
}
return $result;
}
$result = [];
foreach ($property as $key) {
$result[$key] = $this->$key ?? $fallback[$key] ?? null;
}
return $result;
}
return $this->$property ?? $fallback;
}
return $this->$property ?? $fallback;
}
/**
* Converts the object to an array
*
* @return array
*/
public function toArray(): array
{
$result = [];
/**
* Converts the object to an array
*
* @return array
*/
public function toArray(): array
{
$result = [];
foreach ((array)$this as $key => $value) {
if (is_object($value) === true && method_exists($value, 'toArray')) {
$result[$key] = $value->toArray();
} else {
$result[$key] = $value;
}
}
foreach ((array)$this as $key => $value) {
if (is_object($value) === true && method_exists($value, 'toArray')) {
$result[$key] = $value->toArray();
} else {
$result[$key] = $value;
}
}
return $result;
}
return $result;
}
/**
* Converts the object to a json string
*
* @param mixed ...$arguments
* @return string
*/
public function toJson(...$arguments): string
{
return json_encode($this->toArray(), ...$arguments);
}
/**
* Converts the object to a json string
*
* @param mixed ...$arguments
* @return string
*/
public function toJson(...$arguments): string
{
return json_encode($this->toArray(), ...$arguments);
}
}

View File

@@ -16,478 +16,478 @@ use Kirby\Exception\Exception;
*/
class Pagination
{
use Properties {
setProperties as protected baseSetProperties;
}
use Properties {
setProperties as protected baseSetProperties;
}
/**
* The current page
*
* @var int
*/
protected $page;
/**
* The current page
*
* @var int
*/
protected $page;
/**
* Total number of items
*
* @var int
*/
protected $total = 0;
/**
* Total number of items
*
* @var int
*/
protected $total = 0;
/**
* The number of items per page
*
* @var int
*/
protected $limit = 20;
/**
* The number of items per page
*
* @var int
*/
protected $limit = 20;
/**
* Whether validation of the pagination page
* is enabled; will throw Exceptions if true
*
* @var bool
*/
public static $validate = true;
/**
* Whether validation of the pagination page
* is enabled; will throw Exceptions if true
*
* @var bool
*/
public static $validate = true;
/**
* Creates a new pagination object
* with the given parameters
*
* @param array $props
*/
public function __construct(array $props = [])
{
$this->setProperties($props);
}
/**
* Creates a new pagination object
* with the given parameters
*
* @param array $props
*/
public function __construct(array $props = [])
{
$this->setProperties($props);
}
/**
* Creates a pagination instance for the given
* collection with a flexible argument api
*
* @param \Kirby\Toolkit\Collection $collection
* @param mixed ...$arguments
* @return static
*/
public static function for(Collection $collection, ...$arguments)
{
$a = $arguments[0] ?? null;
$b = $arguments[1] ?? null;
/**
* Creates a pagination instance for the given
* collection with a flexible argument api
*
* @param \Kirby\Toolkit\Collection $collection
* @param mixed ...$arguments
* @return static
*/
public static function for(Collection $collection, ...$arguments)
{
$a = $arguments[0] ?? null;
$b = $arguments[1] ?? null;
$params = [];
$params = [];
if (is_a($a, static::class) === true) {
/**
* First argument is a pagination/self object
*/
return $a;
} elseif (is_array($a) === true) {
if (is_a($a, static::class) === true) {
/**
* First argument is a pagination/self object
*/
return $a;
} elseif (is_array($a) === true) {
/**
* First argument is an option array
*
* $collection->paginate([...])
*/
$params = $a;
} elseif (is_int($a) === true && $b === null) {
/**
* First argument is an option array
*
* $collection->paginate([...])
*/
$params = $a;
} elseif (is_int($a) === true && $b === null) {
/**
* First argument is the limit
*
* $collection->paginate(10)
*/
$params['limit'] = $a;
} elseif (is_int($a) === true && is_int($b) === true) {
/**
* First argument is the limit
*
* $collection->paginate(10)
*/
$params['limit'] = $a;
} elseif (is_int($a) === true && is_int($b) === true) {
/**
* First argument is the limit,
* second argument is the page
*
* $collection->paginate(10, 2)
*/
$params['limit'] = $a;
$params['page'] = $b;
} elseif (is_int($a) === true && is_array($b) === true) {
/**
* First argument is the limit,
* second argument is the page
*
* $collection->paginate(10, 2)
*/
$params['limit'] = $a;
$params['page'] = $b;
} elseif (is_int($a) === true && is_array($b) === true) {
/**
* First argument is the limit,
* second argument are options
*
* $collection->paginate(10, [...])
*/
$params = $b;
$params['limit'] = $a;
}
/**
* First argument is the limit,
* second argument are options
*
* $collection->paginate(10, [...])
*/
$params = $b;
$params['limit'] = $a;
}
// add the total count from the collection
$params['total'] = $collection->count();
// add the total count from the collection
$params['total'] = $collection->count();
// remove null values to make later merges work properly
$params = array_filter($params);
// remove null values to make later merges work properly
$params = array_filter($params);
// create the pagination instance
return new static($params);
}
// create the pagination instance
return new static($params);
}
/**
* Getter for the current page
*
* @return int
*/
public function page(): int
{
return $this->page;
}
/**
* Getter for the current page
*
* @return int
*/
public function page(): int
{
return $this->page;
}
/**
* Getter for the total number of items
*
* @return int
*/
public function total(): int
{
return $this->total;
}
/**
* Getter for the total number of items
*
* @return int
*/
public function total(): int
{
return $this->total;
}
/**
* Getter for the number of items per page
*
* @return int
*/
public function limit(): int
{
return $this->limit;
}
/**
* Getter for the number of items per page
*
* @return int
*/
public function limit(): int
{
return $this->limit;
}
/**
* Returns the index of the first item on the page
*
* @return int
*/
public function start(): int
{
$index = $this->page() - 1;
/**
* Returns the index of the first item on the page
*
* @return int
*/
public function start(): int
{
$index = $this->page() - 1;
if ($index < 0) {
$index = 0;
}
if ($index < 0) {
$index = 0;
}
return $index * $this->limit() + 1;
}
return $index * $this->limit() + 1;
}
/**
* Returns the index of the last item on the page
*
* @return int
*/
public function end(): int
{
$value = ($this->start() - 1) + $this->limit();
/**
* Returns the index of the last item on the page
*
* @return int
*/
public function end(): int
{
$value = ($this->start() - 1) + $this->limit();
if ($value <= $this->total()) {
return $value;
}
if ($value <= $this->total()) {
return $value;
}
return $this->total();
}
return $this->total();
}
/**
* Returns the total number of pages
*
* @return int
*/
public function pages(): int
{
if ($this->total() === 0) {
return 0;
}
/**
* Returns the total number of pages
*
* @return int
*/
public function pages(): int
{
if ($this->total() === 0) {
return 0;
}
return (int)ceil($this->total() / $this->limit());
}
return (int)ceil($this->total() / $this->limit());
}
/**
* Returns the first page
*
* @return int
*/
public function firstPage(): int
{
return $this->total() === 0 ? 0 : 1;
}
/**
* Returns the first page
*
* @return int
*/
public function firstPage(): int
{
return $this->total() === 0 ? 0 : 1;
}
/**
* Returns the last page
*
* @return int
*/
public function lastPage(): int
{
return $this->pages();
}
/**
* Returns the last page
*
* @return int
*/
public function lastPage(): int
{
return $this->pages();
}
/**
* Returns the offset (i.e. for db queries)
*
* @return int
*/
public function offset(): int
{
return $this->start() - 1;
}
/**
* Returns the offset (i.e. for db queries)
*
* @return int
*/
public function offset(): int
{
return $this->start() - 1;
}
/**
* Checks if the given page exists
*
* @param int $page
* @return bool
*/
public function hasPage(int $page): bool
{
if ($page <= 0) {
return false;
}
/**
* Checks if the given page exists
*
* @param int $page
* @return bool
*/
public function hasPage(int $page): bool
{
if ($page <= 0) {
return false;
}
if ($page > $this->pages()) {
return false;
}
if ($page > $this->pages()) {
return false;
}
return true;
}
return true;
}
/**
* Checks if there are any pages at all
*
* @return bool
*/
public function hasPages(): bool
{
return $this->total() > $this->limit();
}
/**
* Checks if there are any pages at all
*
* @return bool
*/
public function hasPages(): bool
{
return $this->total() > $this->limit();
}
/**
* Checks if there's a previous page
*
* @return bool
*/
public function hasPrevPage(): bool
{
return $this->page() > 1;
}
/**
* Checks if there's a previous page
*
* @return bool
*/
public function hasPrevPage(): bool
{
return $this->page() > 1;
}
/**
* Returns the previous page
*
* @return int|null
*/
public function prevPage()
{
return $this->hasPrevPage() ? $this->page() - 1 : null;
}
/**
* Returns the previous page
*
* @return int|null
*/
public function prevPage()
{
return $this->hasPrevPage() ? $this->page() - 1 : null;
}
/**
* Checks if there's a next page
*
* @return bool
*/
public function hasNextPage(): bool
{
return $this->end() < $this->total();
}
/**
* Checks if there's a next page
*
* @return bool
*/
public function hasNextPage(): bool
{
return $this->end() < $this->total();
}
/**
* Returns the next page
*
* @return int|null
*/
public function nextPage()
{
return $this->hasNextPage() ? $this->page() + 1 : null;
}
/**
* Returns the next page
*
* @return int|null
*/
public function nextPage()
{
return $this->hasNextPage() ? $this->page() + 1 : null;
}
/**
* Checks if the current page is the first page
*
* @return bool
*/
public function isFirstPage(): bool
{
return $this->page() === $this->firstPage();
}
/**
* Checks if the current page is the first page
*
* @return bool
*/
public function isFirstPage(): bool
{
return $this->page() === $this->firstPage();
}
/**
* Checks if the current page is the last page
*
* @return bool
*/
public function isLastPage(): bool
{
return $this->page() === $this->lastPage();
}
/**
* Checks if the current page is the last page
*
* @return bool
*/
public function isLastPage(): bool
{
return $this->page() === $this->lastPage();
}
/**
* Creates a range of page numbers for Google-like pagination
*
* @param int $range
* @return array
*/
public function range(int $range = 5): array
{
$page = $this->page();
$pages = $this->pages();
$start = 1;
$end = $pages;
/**
* Creates a range of page numbers for Google-like pagination
*
* @param int $range
* @return array
*/
public function range(int $range = 5): array
{
$page = $this->page();
$pages = $this->pages();
$start = 1;
$end = $pages;
if ($pages <= $range) {
return range($start, $end);
}
if ($pages <= $range) {
return range($start, $end);
}
$middle = (int)floor($range/2);
$start = $page - $middle + ($range % 2 === 0);
$end = $start + $range - 1;
$middle = (int)floor($range/2);
$start = $page - $middle + ($range % 2 === 0);
$end = $start + $range - 1;
if ($start <= 0) {
$end = $range;
$start = 1;
}
if ($start <= 0) {
$end = $range;
$start = 1;
}
if ($end > $pages) {
$start = $pages - $range + 1;
$end = $pages;
}
if ($end > $pages) {
$start = $pages - $range + 1;
$end = $pages;
}
return range($start, $end);
}
return range($start, $end);
}
/**
* Returns the first page of the created range
*
* @param int $range
* @return int
*/
public function rangeStart(int $range = 5): int
{
return $this->range($range)[0];
}
/**
* Returns the first page of the created range
*
* @param int $range
* @return int
*/
public function rangeStart(int $range = 5): int
{
return $this->range($range)[0];
}
/**
* Returns the last page of the created range
*
* @param int $range
* @return int
*/
public function rangeEnd(int $range = 5): int
{
$range = $this->range($range);
return array_pop($range);
}
/**
* Returns the last page of the created range
*
* @param int $range
* @return int
*/
public function rangeEnd(int $range = 5): int
{
$range = $this->range($range);
return array_pop($range);
}
/**
* Sets the properties limit, total and page
* and validates that the properties match
*
* @param array $props Array with keys limit, total and/or page
* @return $this
*/
protected function setProperties(array $props)
{
$this->baseSetProperties($props);
/**
* Sets the properties limit, total and page
* and validates that the properties match
*
* @param array $props Array with keys limit, total and/or page
* @return $this
*/
protected function setProperties(array $props)
{
$this->baseSetProperties($props);
// ensure that page is set to something, otherwise
// generate "default page" based on other params
if ($this->page === null) {
$this->page = $this->firstPage();
}
// ensure that page is set to something, otherwise
// generate "default page" based on other params
if ($this->page === null) {
$this->page = $this->firstPage();
}
// allow a page value of 1 even if there are no pages;
// otherwise the exception will get thrown for this pretty common case
$min = $this->firstPage();
$max = $this->pages();
if ($this->page === 1 && $max === 0) {
$this->page = 0;
}
// allow a page value of 1 even if there are no pages;
// otherwise the exception will get thrown for this pretty common case
$min = $this->firstPage();
$max = $this->pages();
if ($this->page === 1 && $max === 0) {
$this->page = 0;
}
// validate page based on all params if validation is enabled,
// otherwise limit the page number to the bounds
if ($this->page < $min || $this->page > $max) {
if (static::$validate === true) {
throw new ErrorPageException('Pagination page ' . $this->page . ' does not exist, expected ' . $min . '-' . $max);
} else {
$this->page = max(min($this->page, $max), $min);
}
}
// validate page based on all params if validation is enabled,
// otherwise limit the page number to the bounds
if ($this->page < $min || $this->page > $max) {
if (static::$validate === true) {
throw new ErrorPageException('Pagination page ' . $this->page . ' does not exist, expected ' . $min . '-' . $max);
} else {
$this->page = max(min($this->page, $max), $min);
}
}
return $this;
}
return $this;
}
/**
* Sets the number of items per page
*
* @param int $limit
* @return $this
*/
protected function setLimit(int $limit = 20)
{
if ($limit < 1) {
throw new Exception('Invalid pagination limit: ' . $limit);
}
/**
* Sets the number of items per page
*
* @param int $limit
* @return $this
*/
protected function setLimit(int $limit = 20)
{
if ($limit < 1) {
throw new Exception('Invalid pagination limit: ' . $limit);
}
$this->limit = $limit;
return $this;
}
$this->limit = $limit;
return $this;
}
/**
* Sets the total number of items
*
* @param int $total
* @return $this
*/
protected function setTotal(int $total = 0)
{
if ($total < 0) {
throw new Exception('Invalid total number of items: ' . $total);
}
/**
* Sets the total number of items
*
* @param int $total
* @return $this
*/
protected function setTotal(int $total = 0)
{
if ($total < 0) {
throw new Exception('Invalid total number of items: ' . $total);
}
$this->total = $total;
return $this;
}
$this->total = $total;
return $this;
}
/**
* Sets the current page
*
* @param int|string|null $page Int or int in string form;
* automatically determined if null
* @return $this
*/
protected function setPage($page = null)
{
// if $page is null, it is set to a default in the setProperties() method
if ($page !== null) {
if (is_numeric($page) !== true || $page < 0) {
throw new Exception('Invalid page number: ' . $page);
}
/**
* Sets the current page
*
* @param int|string|null $page Int or int in string form;
* automatically determined if null
* @return $this
*/
protected function setPage($page = null)
{
// if $page is null, it is set to a default in the setProperties() method
if ($page !== null) {
if (is_numeric($page) !== true || $page < 0) {
throw new Exception('Invalid page number: ' . $page);
}
$this->page = (int)$page;
}
$this->page = (int)$page;
}
return $this;
}
return $this;
}
/**
* Returns an array with all properties
*
* @return array
*/
public function toArray(): array
{
return [
'page' => $this->page(),
'firstPage' => $this->firstPage(),
'lastPage' => $this->lastPage(),
'pages' => $this->pages(),
'offset' => $this->offset(),
'limit' => $this->limit(),
'total' => $this->total(),
'start' => $this->start(),
'end' => $this->end(),
];
}
/**
* Returns an array with all properties
*
* @return array
*/
public function toArray(): array
{
return [
'page' => $this->page(),
'firstPage' => $this->firstPage(),
'lastPage' => $this->lastPage(),
'pages' => $this->pages(),
'offset' => $this->offset(),
'limit' => $this->limit(),
'total' => $this->total(),
'start' => $this->start(),
'end' => $this->end(),
];
}
}

View File

@@ -16,136 +16,136 @@ use ReflectionMethod;
*/
trait Properties
{
protected $propertyData = [];
protected $propertyData = [];
/**
* Creates an instance with the same
* initial properties.
*
* @param array $props
* @return static
*/
public function clone(array $props = [])
{
return new static(array_replace_recursive($this->propertyData, $props));
}
/**
* Creates an instance with the same
* initial properties.
*
* @param array $props
* @return static
*/
public function clone(array $props = [])
{
return new static(array_replace_recursive($this->propertyData, $props));
}
/**
* Creates a clone and fetches all
* lazy-loaded getters to get a full copy
*
* @return static
*/
public function hardcopy()
{
$clone = $this->clone();
$clone->propertiesToArray();
return $clone;
}
/**
* Creates a clone and fetches all
* lazy-loaded getters to get a full copy
*
* @return static
*/
public function hardcopy()
{
$clone = $this->clone();
$clone->propertiesToArray();
return $clone;
}
protected function isRequiredProperty(string $name): bool
{
$method = new ReflectionMethod($this, 'set' . $name);
return $method->getNumberOfRequiredParameters() > 0;
}
protected function isRequiredProperty(string $name): bool
{
$method = new ReflectionMethod($this, 'set' . $name);
return $method->getNumberOfRequiredParameters() > 0;
}
protected function propertiesToArray()
{
$array = [];
protected function propertiesToArray()
{
$array = [];
foreach (get_object_vars($this) as $name => $default) {
if ($name === 'propertyData') {
continue;
}
foreach (get_object_vars($this) as $name => $default) {
if ($name === 'propertyData') {
continue;
}
if (method_exists($this, 'convert' . $name . 'ToArray') === true) {
$array[$name] = $this->{'convert' . $name . 'ToArray'}();
continue;
}
if (method_exists($this, 'convert' . $name . 'ToArray') === true) {
$array[$name] = $this->{'convert' . $name . 'ToArray'}();
continue;
}
if (method_exists($this, $name) === true) {
$method = new ReflectionMethod($this, $name);
if (method_exists($this, $name) === true) {
$method = new ReflectionMethod($this, $name);
if ($method->isPublic() === true) {
$value = $this->$name();
if ($method->isPublic() === true) {
$value = $this->$name();
if (is_object($value) === false) {
$array[$name] = $value;
}
}
}
}
if (is_object($value) === false) {
$array[$name] = $value;
}
}
}
}
ksort($array);
ksort($array);
return $array;
}
return $array;
}
protected function setOptionalProperties(array $props, array $optional)
{
$this->propertyData = array_merge($this->propertyData, $props);
protected function setOptionalProperties(array $props, array $optional)
{
$this->propertyData = array_merge($this->propertyData, $props);
foreach ($optional as $propertyName) {
if (isset($props[$propertyName]) === true) {
$this->{'set' . $propertyName}($props[$propertyName]);
} else {
$this->{'set' . $propertyName}();
}
}
}
foreach ($optional as $propertyName) {
if (isset($props[$propertyName]) === true) {
$this->{'set' . $propertyName}($props[$propertyName]);
} else {
$this->{'set' . $propertyName}();
}
}
}
protected function setProperties($props, array $keys = null)
{
foreach (get_object_vars($this) as $name => $default) {
if ($name === 'propertyData') {
continue;
}
protected function setProperties($props, array $keys = null)
{
foreach (get_object_vars($this) as $name => $default) {
if ($name === 'propertyData') {
continue;
}
$this->setProperty($name, $props[$name] ?? $default);
}
$this->setProperty($name, $props[$name] ?? $default);
}
return $this;
}
return $this;
}
protected function setProperty($name, $value, $required = null)
{
// use a setter if it exists
if (method_exists($this, 'set' . $name) === false) {
return $this;
}
protected function setProperty($name, $value, $required = null)
{
// use a setter if it exists
if (method_exists($this, 'set' . $name) === false) {
return $this;
}
// fetch the default value from the property
$value ??= $this->$name ?? null;
// fetch the default value from the property
$value ??= $this->$name ?? null;
// store all original properties, to be able to clone them later
$this->propertyData[$name] = $value;
// store all original properties, to be able to clone them later
$this->propertyData[$name] = $value;
// handle empty values
if ($value === null) {
// handle empty values
if ($value === null) {
// replace null with a default value, if a default handler exists
if (method_exists($this, 'default' . $name) === true) {
$value = $this->{'default' . $name}();
}
// replace null with a default value, if a default handler exists
if (method_exists($this, 'default' . $name) === true) {
$value = $this->{'default' . $name}();
}
// check for required properties
if ($value === null && ($required ?? $this->isRequiredProperty($name)) === true) {
throw new Exception(sprintf('The property "%s" is required', $name));
}
}
// check for required properties
if ($value === null && ($required ?? $this->isRequiredProperty($name)) === true) {
throw new Exception(sprintf('The property "%s" is required', $name));
}
}
// call the setter with the final value
return $this->{'set' . $name}($value);
}
// call the setter with the final value
return $this->{'set' . $name}($value);
}
protected function setRequiredProperties(array $props, array $required)
{
foreach ($required as $propertyName) {
if (isset($props[$propertyName]) !== true) {
throw new Exception(sprintf('The property "%s" is required', $propertyName));
}
protected function setRequiredProperties(array $props, array $required)
{
foreach ($required as $propertyName) {
if (isset($props[$propertyName]) !== true) {
throw new Exception(sprintf('The property "%s" is required', $propertyName));
}
$this->{'set' . $propertyName}($props[$propertyName]);
}
}
$this->{'set' . $propertyName}($props[$propertyName]);
}
}
}

View File

@@ -18,227 +18,227 @@ use Kirby\Exception\InvalidArgumentException;
*/
class Query
{
public const PARTS = '!\.|(\(([^()]+|(?1))*+\))(*SKIP)(*FAIL)!'; // split by dot, but not inside (nested) parens
public const PARAMETERS = '!,|' . self::SKIP . '!'; // split by comma, but not inside skip groups
public const PARTS = '!\.|(\(([^()]+|(?1))*+\))(*SKIP)(*FAIL)!'; // split by dot, but not inside (nested) parens
public const PARAMETERS = '!,|' . self::SKIP . '!'; // split by comma, but not inside skip groups
public const NO_PNTH = '\([^(]+\)(*SKIP)(*FAIL)';
public const NO_SQBR = '\[[^]]+\](*SKIP)(*FAIL)';
public const NO_DLQU = '\"(?:[^"\\\\]|\\\\.)*\"(*SKIP)(*FAIL)'; // allow \" escaping inside string
public const NO_SLQU = '\'(?:[^\'\\\\]|\\\\.)*\'(*SKIP)(*FAIL)'; // allow \' escaping inside string
public const SKIP = self::NO_PNTH . '|' . self::NO_SQBR . '|' .
self::NO_DLQU . '|' . self::NO_SLQU;
public const NO_PNTH = '\([^(]+\)(*SKIP)(*FAIL)';
public const NO_SQBR = '\[[^]]+\](*SKIP)(*FAIL)';
public const NO_DLQU = '\"(?:[^"\\\\]|\\\\.)*\"(*SKIP)(*FAIL)'; // allow \" escaping inside string
public const NO_SLQU = '\'(?:[^\'\\\\]|\\\\.)*\'(*SKIP)(*FAIL)'; // allow \' escaping inside string
public const SKIP = self::NO_PNTH . '|' . self::NO_SQBR . '|' .
self::NO_DLQU . '|' . self::NO_SLQU;
/**
* The query string
*
* @var string
*/
protected $query;
/**
* The query string
*
* @var string
*/
protected $query;
/**
* Queryable data
*
* @var array
*/
protected $data;
/**
* Queryable data
*
* @var array
*/
protected $data;
/**
* Creates a new Query object
*
* @param string|null $query
* @param array|object $data
*/
public function __construct(?string $query = null, $data = [])
{
$this->query = $query;
$this->data = $data;
}
/**
* Creates a new Query object
*
* @param string|null $query
* @param array|object $data
*/
public function __construct(?string $query = null, $data = [])
{
$this->query = $query;
$this->data = $data;
}
/**
* Returns the query result if anything
* can be found, otherwise returns null
*
* @return mixed
*/
public function result()
{
if (empty($this->query) === true) {
return $this->data;
}
/**
* Returns the query result if anything
* can be found, otherwise returns null
*
* @return mixed
*/
public function result()
{
if (empty($this->query) === true) {
return $this->data;
}
return $this->resolve($this->query);
}
return $this->resolve($this->query);
}
/**
* Resolves the query if anything
* can be found, otherwise returns null
*
* @param string $query
* @return mixed
*
* @throws \Kirby\Exception\BadMethodCallException If an invalid method is accessed by the query
*/
protected function resolve(string $query)
{
// direct key access in arrays
if (is_array($this->data) === true && array_key_exists($query, $this->data) === true) {
return $this->data[$query];
}
/**
* Resolves the query if anything
* can be found, otherwise returns null
*
* @param string $query
* @return mixed
*
* @throws \Kirby\Exception\BadMethodCallException If an invalid method is accessed by the query
*/
protected function resolve(string $query)
{
// direct key access in arrays
if (is_array($this->data) === true && array_key_exists($query, $this->data) === true) {
return $this->data[$query];
}
$parts = $this->parts($query);
$data = $this->data;
$value = null;
$parts = $this->parts($query);
$data = $this->data;
$value = null;
foreach ($parts as $part) {
$info = $this->part($part);
$method = $info['method'];
$args = $info['args'];
foreach ($parts as $part) {
$info = $this->part($part);
$method = $info['method'];
$args = $info['args'];
if (is_array($data)) {
if (array_key_exists($method, $data) === true) {
$value = $data[$method];
if (is_array($data)) {
if (array_key_exists($method, $data) === true) {
$value = $data[$method];
if (is_a($value, 'Closure') === true) {
$value = $value(...$args);
} elseif ($args !== []) {
throw new InvalidArgumentException('Cannot access array element ' . $method . ' with arguments');
}
} else {
static::accessError($data, $method, 'property');
}
} elseif (is_object($data)) {
if (
method_exists($data, $method) === true ||
method_exists($data, '__call') === true
) {
$value = $data->$method(...$args);
} elseif (
$args === [] && (
property_exists($data, $method) === true ||
method_exists($data, '__get') === true
)
) {
$value = $data->$method;
} else {
$label = ($args === []) ? 'method/property' : 'method';
static::accessError($data, $method, $label);
}
} else {
// further parts on a scalar/null value
static::accessError($data, $method, 'method/property');
}
if (is_a($value, 'Closure') === true) {
$value = $value(...$args);
} elseif ($args !== []) {
throw new InvalidArgumentException('Cannot access array element ' . $method . ' with arguments');
}
} else {
static::accessError($data, $method, 'property');
}
} elseif (is_object($data)) {
if (
method_exists($data, $method) === true ||
method_exists($data, '__call') === true
) {
$value = $data->$method(...$args);
} elseif (
$args === [] && (
property_exists($data, $method) === true ||
method_exists($data, '__get') === true
)
) {
$value = $data->$method;
} else {
$label = ($args === []) ? 'method/property' : 'method';
static::accessError($data, $method, $label);
}
} else {
// further parts on a scalar/null value
static::accessError($data, $method, 'method/property');
}
// continue with the current value for the next part
$data = $value;
}
// continue with the current value for the next part
$data = $value;
}
return $value;
}
return $value;
}
/**
* Breaks the query string down into its components
*
* @param string $query
* @return array
*/
protected function parts(string $query): array
{
return preg_split(self::PARTS, trim($query), -1, PREG_SPLIT_NO_EMPTY);
}
/**
* Breaks the query string down into its components
*
* @param string $query
* @return array
*/
protected function parts(string $query): array
{
return preg_split(self::PARTS, trim($query), -1, PREG_SPLIT_NO_EMPTY);
}
/**
* Analyzes each part of the query string and
* extracts methods and method arguments
*
* @param string $part
* @return array
*/
protected function part(string $part): array
{
if (Str::endsWith($part, ')') === true) {
$method = Str::before($part, '(');
/**
* Analyzes each part of the query string and
* extracts methods and method arguments
*
* @param string $part
* @return array
*/
protected function part(string $part): array
{
if (Str::endsWith($part, ')') === true) {
$method = Str::before($part, '(');
// the args are everything inside the *outer* parentheses
$args = Str::substr($part, Str::position($part, '(') + 1, -1);
$args = preg_split(self::PARAMETERS, $args);
$args = array_map('self::parameter', $args);
// the args are everything inside the *outer* parentheses
$args = Str::substr($part, Str::position($part, '(') + 1, -1);
$args = preg_split(self::PARAMETERS, $args);
$args = array_map('self::parameter', $args);
return compact('method', 'args');
} else {
return [
'method' => $part,
'args' => []
];
}
}
return compact('method', 'args');
} else {
return [
'method' => $part,
'args' => []
];
}
}
/**
* Converts a parameter of a query to
* its proper native PHP type
*
* @param string $arg
* @return mixed
*/
protected function parameter(string $arg)
{
$arg = trim($arg);
/**
* Converts a parameter of a query to
* its proper native PHP type
*
* @param string $arg
* @return mixed
*/
protected function parameter(string $arg)
{
$arg = trim($arg);
// string with double quotes
if (substr($arg, 0, 1) === '"' && substr($arg, -1) === '"') {
return str_replace('\"', '"', substr($arg, 1, -1));
}
// string with double quotes
if (substr($arg, 0, 1) === '"' && substr($arg, -1) === '"') {
return str_replace('\"', '"', substr($arg, 1, -1));
}
// string with single quotes
if (substr($arg, 0, 1) === "'" && substr($arg, -1) === "'") {
return str_replace("\'", "'", substr($arg, 1, -1));
}
// string with single quotes
if (substr($arg, 0, 1) === "'" && substr($arg, -1) === "'") {
return str_replace("\'", "'", substr($arg, 1, -1));
}
// boolean or null
switch ($arg) {
case 'null':
return null;
case 'false':
return false;
case 'true':
return true;
}
// boolean or null
switch ($arg) {
case 'null':
return null;
case 'false':
return false;
case 'true':
return true;
}
// numeric
if (is_numeric($arg) === true) {
return (float)$arg;
}
// numeric
if (is_numeric($arg) === true) {
return (float)$arg;
}
// array: split and recursive sanitizing
if (substr($arg, 0, 1) === '[' && substr($arg, -1) === ']') {
$arg = substr($arg, 1, -1);
$arg = preg_split(self::PARAMETERS, $arg);
return array_map('self::parameter', $arg);
}
// array: split and recursive sanitizing
if (substr($arg, 0, 1) === '[' && substr($arg, -1) === ']') {
$arg = substr($arg, 1, -1);
$arg = preg_split(self::PARAMETERS, $arg);
return array_map('self::parameter', $arg);
}
// resolve parameter for objects and methods itself
return $this->resolve($arg);
}
// resolve parameter for objects and methods itself
return $this->resolve($arg);
}
/**
* Throws an exception for an access to an invalid method
*
* @param mixed $data Variable on which the access was tried
* @param string $name Name of the method/property that was accessed
* @param string $label Type of the name (`method`, `property` or `method/property`)
* @return void
*
* @throws \Kirby\Exception\BadMethodCallException
*/
protected static function accessError($data, string $name, string $label): void
{
$type = strtolower(gettype($data));
if ($type === 'double') {
$type = 'float';
}
/**
* Throws an exception for an access to an invalid method
*
* @param mixed $data Variable on which the access was tried
* @param string $name Name of the method/property that was accessed
* @param string $label Type of the name (`method`, `property` or `method/property`)
* @return void
*
* @throws \Kirby\Exception\BadMethodCallException
*/
protected static function accessError($data, string $name, string $label): void
{
$type = strtolower(gettype($data));
if ($type === 'double') {
$type = 'float';
}
$nonExisting = in_array($type, ['array', 'object']) ? 'non-existing ' : '';
$nonExisting = in_array($type, ['array', 'object']) ? 'non-existing ' : '';
$error = 'Access to ' . $nonExisting . $label . ' ' . $name . ' on ' . $type;
throw new BadMethodCallException($error);
}
$error = 'Access to ' . $nonExisting . $label . ' ' . $name . ' on ' . $type;
throw new BadMethodCallException($error);
}
}

View File

@@ -15,59 +15,59 @@ namespace Kirby\Toolkit;
*/
class Silo
{
/**
* @var array
*/
public static $data = [];
/**
* @var array
*/
public static $data = [];
/**
* Setter for new data.
*
* @param string|array $key
* @param mixed $value
* @return array
*/
public static function set($key, $value = null): array
{
if (is_array($key) === true) {
return static::$data = array_merge(static::$data, $key);
} else {
static::$data[$key] = $value;
return static::$data;
}
}
/**
* Setter for new data.
*
* @param string|array $key
* @param mixed $value
* @return array
*/
public static function set($key, $value = null): array
{
if (is_array($key) === true) {
return static::$data = array_merge(static::$data, $key);
} else {
static::$data[$key] = $value;
return static::$data;
}
}
/**
* @param string|array $key
* @param mixed $default
* @return mixed
*/
public static function get($key = null, $default = null)
{
if ($key === null) {
return static::$data;
}
/**
* @param string|array $key
* @param mixed $default
* @return mixed
*/
public static function get($key = null, $default = null)
{
if ($key === null) {
return static::$data;
}
return A::get(static::$data, $key, $default);
}
return A::get(static::$data, $key, $default);
}
/**
* Removes an item from the data array
*
* @param string|null $key
* @return array
*/
public static function remove(string $key = null): array
{
// reset the entire array
if ($key === null) {
return static::$data = [];
}
/**
* Removes an item from the data array
*
* @param string|null $key
* @return array
*/
public static function remove(string $key = null): array
{
// reset the entire array
if ($key === null) {
return static::$data = [];
}
// unset a single key
unset(static::$data[$key]);
// unset a single key
unset(static::$data[$key]);
// return the array without the removed key
return static::$data;
}
// return the array without the removed key
return static::$data;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,36 +16,36 @@ use Throwable;
*/
class Tpl
{
/**
* Renders the template
*
* @param string|null $file
* @param array $data
* @return string
* @throws Throwable
*/
public static function load(?string $file = null, array $data = []): string
{
if ($file === null || is_file($file) === false) {
return '';
}
/**
* Renders the template
*
* @param string|null $file
* @param array $data
* @return string
* @throws Throwable
*/
public static function load(?string $file = null, array $data = []): string
{
if ($file === null || is_file($file) === false) {
return '';
}
ob_start();
ob_start();
$exception = null;
try {
F::load($file, null, $data);
} catch (Throwable $e) {
$exception = $e;
}
$exception = null;
try {
F::load($file, null, $data);
} catch (Throwable $e) {
$exception = $e;
}
$content = ob_get_contents();
ob_end_clean();
$content = ob_get_contents();
ob_end_clean();
if ($exception === null) {
return $content;
}
if ($exception === null) {
return $content;
}
throw $exception;
}
throw $exception;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,121 +17,121 @@ use Throwable;
*/
class View
{
/**
* The absolute path to the view file
*
* @var string
*/
protected $file;
/**
* The absolute path to the view file
*
* @var string
*/
protected $file;
/**
* The view data
*
* @var array
*/
protected $data = [];
/**
* The view data
*
* @var array
*/
protected $data = [];
/**
* Creates a new view object
*
* @param string $file
* @param array $data
*/
public function __construct(string $file, array $data = [])
{
$this->file = $file;
$this->data = $data;
}
/**
* Creates a new view object
*
* @param string $file
* @param array $data
*/
public function __construct(string $file, array $data = [])
{
$this->file = $file;
$this->data = $data;
}
/**
* Returns the view's data array
* without globals.
*
* @return array
*/
public function data(): array
{
return $this->data;
}
/**
* Returns the view's data array
* without globals.
*
* @return array
*/
public function data(): array
{
return $this->data;
}
/**
* Checks if the template file exists
*
* @return bool
*/
public function exists(): bool
{
return is_file($this->file()) === true;
}
/**
* Checks if the template file exists
*
* @return bool
*/
public function exists(): bool
{
return is_file($this->file()) === true;
}
/**
* Returns the view file
*
* @return string|false
*/
public function file()
{
return $this->file;
}
/**
* Returns the view file
*
* @return string|false
*/
public function file()
{
return $this->file;
}
/**
* Creates an error message for the missing view exception
*
* @return string
*/
protected function missingViewMessage(): string
{
return 'The view does not exist: ' . $this->file();
}
/**
* Creates an error message for the missing view exception
*
* @return string
*/
protected function missingViewMessage(): string
{
return 'The view does not exist: ' . $this->file();
}
/**
* Renders the view
*
* @return string
*/
public function render(): string
{
if ($this->exists() === false) {
throw new Exception($this->missingViewMessage());
}
/**
* Renders the view
*
* @return string
*/
public function render(): string
{
if ($this->exists() === false) {
throw new Exception($this->missingViewMessage());
}
ob_start();
ob_start();
$exception = null;
try {
F::load($this->file(), null, $this->data());
} catch (Throwable $e) {
$exception = $e;
}
$exception = null;
try {
F::load($this->file(), null, $this->data());
} catch (Throwable $e) {
$exception = $e;
}
$content = ob_get_contents();
ob_end_clean();
$content = ob_get_contents();
ob_end_clean();
if ($exception === null) {
return $content;
}
if ($exception === null) {
return $content;
}
throw $exception;
}
throw $exception;
}
/**
* Alias for View::render()
*
* @return string
*/
public function toString(): string
{
return $this->render();
}
/**
* Alias for View::render()
*
* @return string
*/
public function toString(): string
{
return $this->render();
}
/**
* Magic string converter to enable
* converting view objects to string
*
* @return string
*/
public function __toString(): string
{
return $this->render();
}
/**
* Magic string converter to enable
* converting view objects to string
*
* @return string
*/
public function __toString(): string
{
return $this->render();
}
}

View File

@@ -15,420 +15,420 @@ use SimpleXMLElement;
*/
class Xml
{
/**
* HTML to XML conversion table for entities
*
* @var array
*/
public static $entities = [
'&nbsp;' => '&#160;', '&iexcl;' => '&#161;', '&cent;' => '&#162;', '&pound;' => '&#163;', '&curren;' => '&#164;', '&yen;' => '&#165;', '&brvbar;' => '&#166;', '&sect;' => '&#167;',
'&uml;' => '&#168;', '&copy;' => '&#169;', '&ordf;' => '&#170;', '&laquo;' => '&#171;', '&not;' => '&#172;', '&shy;' => '&#173;', '&reg;' => '&#174;', '&macr;' => '&#175;',
'&deg;' => '&#176;', '&plusmn;' => '&#177;', '&sup2;' => '&#178;', '&sup3;' => '&#179;', '&acute;' => '&#180;', '&micro;' => '&#181;', '&para;' => '&#182;', '&middot;' => '&#183;',
'&cedil;' => '&#184;', '&sup1;' => '&#185;', '&ordm;' => '&#186;', '&raquo;' => '&#187;', '&frac14;' => '&#188;', '&frac12;' => '&#189;', '&frac34;' => '&#190;', '&iquest;' => '&#191;',
'&Agrave;' => '&#192;', '&Aacute;' => '&#193;', '&Acirc;' => '&#194;', '&Atilde;' => '&#195;', '&Auml;' => '&#196;', '&Aring;' => '&#197;', '&AElig;' => '&#198;', '&Ccedil;' => '&#199;',
'&Egrave;' => '&#200;', '&Eacute;' => '&#201;', '&Ecirc;' => '&#202;', '&Euml;' => '&#203;', '&Igrave;' => '&#204;', '&Iacute;' => '&#205;', '&Icirc;' => '&#206;', '&Iuml;' => '&#207;',
'&ETH;' => '&#208;', '&Ntilde;' => '&#209;', '&Ograve;' => '&#210;', '&Oacute;' => '&#211;', '&Ocirc;' => '&#212;', '&Otilde;' => '&#213;', '&Ouml;' => '&#214;', '&times;' => '&#215;',
'&Oslash;' => '&#216;', '&Ugrave;' => '&#217;', '&Uacute;' => '&#218;', '&Ucirc;' => '&#219;', '&Uuml;' => '&#220;', '&Yacute;' => '&#221;', '&THORN;' => '&#222;', '&szlig;' => '&#223;',
'&agrave;' => '&#224;', '&aacute;' => '&#225;', '&acirc;' => '&#226;', '&atilde;' => '&#227;', '&auml;' => '&#228;', '&aring;' => '&#229;', '&aelig;' => '&#230;', '&ccedil;' => '&#231;',
'&egrave;' => '&#232;', '&eacute;' => '&#233;', '&ecirc;' => '&#234;', '&euml;' => '&#235;', '&igrave;' => '&#236;', '&iacute;' => '&#237;', '&icirc;' => '&#238;', '&iuml;' => '&#239;',
'&eth;' => '&#240;', '&ntilde;' => '&#241;', '&ograve;' => '&#242;', '&oacute;' => '&#243;', '&ocirc;' => '&#244;', '&otilde;' => '&#245;', '&ouml;' => '&#246;', '&divide;' => '&#247;',
'&oslash;' => '&#248;', '&ugrave;' => '&#249;', '&uacute;' => '&#250;', '&ucirc;' => '&#251;', '&uuml;' => '&#252;', '&yacute;' => '&#253;', '&thorn;' => '&#254;', '&yuml;' => '&#255;',
'&fnof;' => '&#402;', '&Alpha;' => '&#913;', '&Beta;' => '&#914;', '&Gamma;' => '&#915;', '&Delta;' => '&#916;', '&Epsilon;' => '&#917;', '&Zeta;' => '&#918;', '&Eta;' => '&#919;',
'&Theta;' => '&#920;', '&Iota;' => '&#921;', '&Kappa;' => '&#922;', '&Lambda;' => '&#923;', '&Mu;' => '&#924;', '&Nu;' => '&#925;', '&Xi;' => '&#926;', '&Omicron;' => '&#927;',
'&Pi;' => '&#928;', '&Rho;' => '&#929;', '&Sigma;' => '&#931;', '&Tau;' => '&#932;', '&Upsilon;' => '&#933;', '&Phi;' => '&#934;', '&Chi;' => '&#935;', '&Psi;' => '&#936;',
'&Omega;' => '&#937;', '&alpha;' => '&#945;', '&beta;' => '&#946;', '&gamma;' => '&#947;', '&delta;' => '&#948;', '&epsilon;' => '&#949;', '&zeta;' => '&#950;', '&eta;' => '&#951;',
'&theta;' => '&#952;', '&iota;' => '&#953;', '&kappa;' => '&#954;', '&lambda;' => '&#955;', '&mu;' => '&#956;', '&nu;' => '&#957;', '&xi;' => '&#958;', '&omicron;' => '&#959;',
'&pi;' => '&#960;', '&rho;' => '&#961;', '&sigmaf;' => '&#962;', '&sigma;' => '&#963;', '&tau;' => '&#964;', '&upsilon;' => '&#965;', '&phi;' => '&#966;', '&chi;' => '&#967;',
'&psi;' => '&#968;', '&omega;' => '&#969;', '&thetasym;' => '&#977;', '&upsih;' => '&#978;', '&piv;' => '&#982;', '&bull;' => '&#8226;', '&hellip;' => '&#8230;', '&prime;' => '&#8242;',
'&Prime;' => '&#8243;', '&oline;' => '&#8254;', '&frasl;' => '&#8260;', '&weierp;' => '&#8472;', '&image;' => '&#8465;', '&real;' => '&#8476;', '&trade;' => '&#8482;', '&alefsym;' => '&#8501;',
'&larr;' => '&#8592;', '&uarr;' => '&#8593;', '&rarr;' => '&#8594;', '&darr;' => '&#8595;', '&harr;' => '&#8596;', '&crarr;' => '&#8629;', '&lArr;' => '&#8656;', '&uArr;' => '&#8657;',
'&rArr;' => '&#8658;', '&dArr;' => '&#8659;', '&hArr;' => '&#8660;', '&forall;' => '&#8704;', '&part;' => '&#8706;', '&exist;' => '&#8707;', '&empty;' => '&#8709;', '&nabla;' => '&#8711;',
'&isin;' => '&#8712;', '&notin;' => '&#8713;', '&ni;' => '&#8715;', '&prod;' => '&#8719;', '&sum;' => '&#8721;', '&minus;' => '&#8722;', '&lowast;' => '&#8727;', '&radic;' => '&#8730;',
'&prop;' => '&#8733;', '&infin;' => '&#8734;', '&ang;' => '&#8736;', '&and;' => '&#8743;', '&or;' => '&#8744;', '&cap;' => '&#8745;', '&cup;' => '&#8746;', '&int;' => '&#8747;',
'&there4;' => '&#8756;', '&sim;' => '&#8764;', '&cong;' => '&#8773;', '&asymp;' => '&#8776;', '&ne;' => '&#8800;', '&equiv;' => '&#8801;', '&le;' => '&#8804;', '&ge;' => '&#8805;',
'&sub;' => '&#8834;', '&sup;' => '&#8835;', '&nsub;' => '&#8836;', '&sube;' => '&#8838;', '&supe;' => '&#8839;', '&oplus;' => '&#8853;', '&otimes;' => '&#8855;', '&perp;' => '&#8869;',
'&sdot;' => '&#8901;', '&lceil;' => '&#8968;', '&rceil;' => '&#8969;', '&lfloor;' => '&#8970;', '&rfloor;' => '&#8971;', '&lang;' => '&#9001;', '&rang;' => '&#9002;', '&loz;' => '&#9674;',
'&spades;' => '&#9824;', '&clubs;' => '&#9827;', '&hearts;' => '&#9829;', '&diams;' => '&#9830;', '&quot;' => '&#34;', '&amp;' => '&#38;', '&lt;' => '&#60;', '&gt;' => '&#62;', '&OElig;' => '&#338;',
'&oelig;' => '&#339;', '&Scaron;' => '&#352;', '&scaron;' => '&#353;', '&Yuml;' => '&#376;', '&circ;' => '&#710;', '&tilde;' => '&#732;', '&ensp;' => '&#8194;', '&emsp;' => '&#8195;',
'&thinsp;' => '&#8201;', '&zwnj;' => '&#8204;', '&zwj;' => '&#8205;', '&lrm;' => '&#8206;', '&rlm;' => '&#8207;', '&ndash;' => '&#8211;', '&mdash;' => '&#8212;', '&lsquo;' => '&#8216;',
'&rsquo;' => '&#8217;', '&sbquo;' => '&#8218;', '&ldquo;' => '&#8220;', '&rdquo;' => '&#8221;', '&bdquo;' => '&#8222;', '&dagger;' => '&#8224;', '&Dagger;' => '&#8225;', '&permil;' => '&#8240;',
'&lsaquo;' => '&#8249;', '&rsaquo;' => '&#8250;', '&euro;' => '&#8364;'
];
/**
* HTML to XML conversion table for entities
*
* @var array
*/
public static $entities = [
'&nbsp;' => '&#160;', '&iexcl;' => '&#161;', '&cent;' => '&#162;', '&pound;' => '&#163;', '&curren;' => '&#164;', '&yen;' => '&#165;', '&brvbar;' => '&#166;', '&sect;' => '&#167;',
'&uml;' => '&#168;', '&copy;' => '&#169;', '&ordf;' => '&#170;', '&laquo;' => '&#171;', '&not;' => '&#172;', '&shy;' => '&#173;', '&reg;' => '&#174;', '&macr;' => '&#175;',
'&deg;' => '&#176;', '&plusmn;' => '&#177;', '&sup2;' => '&#178;', '&sup3;' => '&#179;', '&acute;' => '&#180;', '&micro;' => '&#181;', '&para;' => '&#182;', '&middot;' => '&#183;',
'&cedil;' => '&#184;', '&sup1;' => '&#185;', '&ordm;' => '&#186;', '&raquo;' => '&#187;', '&frac14;' => '&#188;', '&frac12;' => '&#189;', '&frac34;' => '&#190;', '&iquest;' => '&#191;',
'&Agrave;' => '&#192;', '&Aacute;' => '&#193;', '&Acirc;' => '&#194;', '&Atilde;' => '&#195;', '&Auml;' => '&#196;', '&Aring;' => '&#197;', '&AElig;' => '&#198;', '&Ccedil;' => '&#199;',
'&Egrave;' => '&#200;', '&Eacute;' => '&#201;', '&Ecirc;' => '&#202;', '&Euml;' => '&#203;', '&Igrave;' => '&#204;', '&Iacute;' => '&#205;', '&Icirc;' => '&#206;', '&Iuml;' => '&#207;',
'&ETH;' => '&#208;', '&Ntilde;' => '&#209;', '&Ograve;' => '&#210;', '&Oacute;' => '&#211;', '&Ocirc;' => '&#212;', '&Otilde;' => '&#213;', '&Ouml;' => '&#214;', '&times;' => '&#215;',
'&Oslash;' => '&#216;', '&Ugrave;' => '&#217;', '&Uacute;' => '&#218;', '&Ucirc;' => '&#219;', '&Uuml;' => '&#220;', '&Yacute;' => '&#221;', '&THORN;' => '&#222;', '&szlig;' => '&#223;',
'&agrave;' => '&#224;', '&aacute;' => '&#225;', '&acirc;' => '&#226;', '&atilde;' => '&#227;', '&auml;' => '&#228;', '&aring;' => '&#229;', '&aelig;' => '&#230;', '&ccedil;' => '&#231;',
'&egrave;' => '&#232;', '&eacute;' => '&#233;', '&ecirc;' => '&#234;', '&euml;' => '&#235;', '&igrave;' => '&#236;', '&iacute;' => '&#237;', '&icirc;' => '&#238;', '&iuml;' => '&#239;',
'&eth;' => '&#240;', '&ntilde;' => '&#241;', '&ograve;' => '&#242;', '&oacute;' => '&#243;', '&ocirc;' => '&#244;', '&otilde;' => '&#245;', '&ouml;' => '&#246;', '&divide;' => '&#247;',
'&oslash;' => '&#248;', '&ugrave;' => '&#249;', '&uacute;' => '&#250;', '&ucirc;' => '&#251;', '&uuml;' => '&#252;', '&yacute;' => '&#253;', '&thorn;' => '&#254;', '&yuml;' => '&#255;',
'&fnof;' => '&#402;', '&Alpha;' => '&#913;', '&Beta;' => '&#914;', '&Gamma;' => '&#915;', '&Delta;' => '&#916;', '&Epsilon;' => '&#917;', '&Zeta;' => '&#918;', '&Eta;' => '&#919;',
'&Theta;' => '&#920;', '&Iota;' => '&#921;', '&Kappa;' => '&#922;', '&Lambda;' => '&#923;', '&Mu;' => '&#924;', '&Nu;' => '&#925;', '&Xi;' => '&#926;', '&Omicron;' => '&#927;',
'&Pi;' => '&#928;', '&Rho;' => '&#929;', '&Sigma;' => '&#931;', '&Tau;' => '&#932;', '&Upsilon;' => '&#933;', '&Phi;' => '&#934;', '&Chi;' => '&#935;', '&Psi;' => '&#936;',
'&Omega;' => '&#937;', '&alpha;' => '&#945;', '&beta;' => '&#946;', '&gamma;' => '&#947;', '&delta;' => '&#948;', '&epsilon;' => '&#949;', '&zeta;' => '&#950;', '&eta;' => '&#951;',
'&theta;' => '&#952;', '&iota;' => '&#953;', '&kappa;' => '&#954;', '&lambda;' => '&#955;', '&mu;' => '&#956;', '&nu;' => '&#957;', '&xi;' => '&#958;', '&omicron;' => '&#959;',
'&pi;' => '&#960;', '&rho;' => '&#961;', '&sigmaf;' => '&#962;', '&sigma;' => '&#963;', '&tau;' => '&#964;', '&upsilon;' => '&#965;', '&phi;' => '&#966;', '&chi;' => '&#967;',
'&psi;' => '&#968;', '&omega;' => '&#969;', '&thetasym;' => '&#977;', '&upsih;' => '&#978;', '&piv;' => '&#982;', '&bull;' => '&#8226;', '&hellip;' => '&#8230;', '&prime;' => '&#8242;',
'&Prime;' => '&#8243;', '&oline;' => '&#8254;', '&frasl;' => '&#8260;', '&weierp;' => '&#8472;', '&image;' => '&#8465;', '&real;' => '&#8476;', '&trade;' => '&#8482;', '&alefsym;' => '&#8501;',
'&larr;' => '&#8592;', '&uarr;' => '&#8593;', '&rarr;' => '&#8594;', '&darr;' => '&#8595;', '&harr;' => '&#8596;', '&crarr;' => '&#8629;', '&lArr;' => '&#8656;', '&uArr;' => '&#8657;',
'&rArr;' => '&#8658;', '&dArr;' => '&#8659;', '&hArr;' => '&#8660;', '&forall;' => '&#8704;', '&part;' => '&#8706;', '&exist;' => '&#8707;', '&empty;' => '&#8709;', '&nabla;' => '&#8711;',
'&isin;' => '&#8712;', '&notin;' => '&#8713;', '&ni;' => '&#8715;', '&prod;' => '&#8719;', '&sum;' => '&#8721;', '&minus;' => '&#8722;', '&lowast;' => '&#8727;', '&radic;' => '&#8730;',
'&prop;' => '&#8733;', '&infin;' => '&#8734;', '&ang;' => '&#8736;', '&and;' => '&#8743;', '&or;' => '&#8744;', '&cap;' => '&#8745;', '&cup;' => '&#8746;', '&int;' => '&#8747;',
'&there4;' => '&#8756;', '&sim;' => '&#8764;', '&cong;' => '&#8773;', '&asymp;' => '&#8776;', '&ne;' => '&#8800;', '&equiv;' => '&#8801;', '&le;' => '&#8804;', '&ge;' => '&#8805;',
'&sub;' => '&#8834;', '&sup;' => '&#8835;', '&nsub;' => '&#8836;', '&sube;' => '&#8838;', '&supe;' => '&#8839;', '&oplus;' => '&#8853;', '&otimes;' => '&#8855;', '&perp;' => '&#8869;',
'&sdot;' => '&#8901;', '&lceil;' => '&#8968;', '&rceil;' => '&#8969;', '&lfloor;' => '&#8970;', '&rfloor;' => '&#8971;', '&lang;' => '&#9001;', '&rang;' => '&#9002;', '&loz;' => '&#9674;',
'&spades;' => '&#9824;', '&clubs;' => '&#9827;', '&hearts;' => '&#9829;', '&diams;' => '&#9830;', '&quot;' => '&#34;', '&amp;' => '&#38;', '&lt;' => '&#60;', '&gt;' => '&#62;', '&OElig;' => '&#338;',
'&oelig;' => '&#339;', '&Scaron;' => '&#352;', '&scaron;' => '&#353;', '&Yuml;' => '&#376;', '&circ;' => '&#710;', '&tilde;' => '&#732;', '&ensp;' => '&#8194;', '&emsp;' => '&#8195;',
'&thinsp;' => '&#8201;', '&zwnj;' => '&#8204;', '&zwj;' => '&#8205;', '&lrm;' => '&#8206;', '&rlm;' => '&#8207;', '&ndash;' => '&#8211;', '&mdash;' => '&#8212;', '&lsquo;' => '&#8216;',
'&rsquo;' => '&#8217;', '&sbquo;' => '&#8218;', '&ldquo;' => '&#8220;', '&rdquo;' => '&#8221;', '&bdquo;' => '&#8222;', '&dagger;' => '&#8224;', '&Dagger;' => '&#8225;', '&permil;' => '&#8240;',
'&lsaquo;' => '&#8249;', '&rsaquo;' => '&#8250;', '&euro;' => '&#8364;'
];
/**
* Closing string for void tags
*
* @var string
*/
public static $void = ' />';
/**
* Closing string for void tags
*
* @var string
*/
public static $void = ' />';
/**
* Generates a single attribute or a list of attributes
*
* @param string|array $name String: A single attribute with that name will be generated.
* Key-value array: A list of attributes will be generated. Don't pass a second argument in that case.
* @param mixed $value If used with a `$name` string, pass the value of the attribute here.
* If used with a `$name` array, this can be set to `false` to disable attribute sorting.
* @return string|null The generated XML attributes string
*/
public static function attr($name, $value = null): ?string
{
if (is_array($name) === true) {
if ($value !== false) {
ksort($name);
}
/**
* Generates a single attribute or a list of attributes
*
* @param string|array $name String: A single attribute with that name will be generated.
* Key-value array: A list of attributes will be generated. Don't pass a second argument in that case.
* @param mixed $value If used with a `$name` string, pass the value of the attribute here.
* If used with a `$name` array, this can be set to `false` to disable attribute sorting.
* @return string|null The generated XML attributes string
*/
public static function attr($name, $value = null): ?string
{
if (is_array($name) === true) {
if ($value !== false) {
ksort($name);
}
$attributes = [];
foreach ($name as $key => $val) {
$a = static::attr($key, $val);
$attributes = [];
foreach ($name as $key => $val) {
$a = static::attr($key, $val);
if ($a) {
$attributes[] = $a;
}
}
if ($a) {
$attributes[] = $a;
}
}
return implode(' ', $attributes);
}
return implode(' ', $attributes);
}
if ($value === null || $value === '' || $value === []) {
return null;
}
if ($value === null || $value === '' || $value === []) {
return null;
}
if ($value === ' ') {
return strtolower($name) . '=""';
}
if ($value === ' ') {
return strtolower($name) . '=""';
}
if (is_bool($value) === true) {
return $value === true ? strtolower($name) . '="' . strtolower($name) . '"' : null;
}
if (is_bool($value) === true) {
return $value === true ? strtolower($name) . '="' . strtolower($name) . '"' : null;
}
if (is_array($value) === true) {
if (isset($value['value'], $value['escape'])) {
$value = $value['escape'] === true ? static::encode($value['value']) : $value['value'];
} else {
$value = implode(' ', array_filter(
$value,
fn ($value) => !empty($value) || is_numeric($value)
));
}
} else {
$value = static::encode($value);
}
if (is_array($value) === true) {
if (isset($value['value'], $value['escape'])) {
$value = $value['escape'] === true ? static::encode($value['value']) : $value['value'];
} else {
$value = implode(' ', array_filter(
$value,
fn ($value) => !empty($value) || is_numeric($value)
));
}
} else {
$value = static::encode($value);
}
return strtolower($name) . '="' . $value . '"';
}
return strtolower($name) . '="' . $value . '"';
}
/**
* Creates an XML string from an array
*
* Supports special array keys `@name` (element name),
* `@attributes` (XML attribute key-value array),
* `@namespaces` (array with XML namespaces) and
* `@value` (element content)
*
* @param array|string $props The source array or tag content (used internally)
* @param string $name The name of the root element
* @param bool $head Include the XML declaration head or not
* @param string $indent Indentation string, defaults to two spaces
* @param int $level The indentation level (used internally)
* @return string The XML string
*/
public static function create($props, string $name = 'root', bool $head = true, string $indent = ' ', int $level = 0): string
{
if (is_array($props) === true) {
if (A::isAssociative($props) === true) {
// a tag with attributes or named children
/**
* Creates an XML string from an array
*
* Supports special array keys `@name` (element name),
* `@attributes` (XML attribute key-value array),
* `@namespaces` (array with XML namespaces) and
* `@value` (element content)
*
* @param array|string $props The source array or tag content (used internally)
* @param string $name The name of the root element
* @param bool $head Include the XML declaration head or not
* @param string $indent Indentation string, defaults to two spaces
* @param int $level The indentation level (used internally)
* @return string The XML string
*/
public static function create($props, string $name = 'root', bool $head = true, string $indent = ' ', int $level = 0): string
{
if (is_array($props) === true) {
if (A::isAssociative($props) === true) {
// a tag with attributes or named children
// extract metadata from special array keys
$name = $props['@name'] ?? $name;
$attributes = $props['@attributes'] ?? [];
$value = $props['@value'] ?? null;
if (isset($props['@namespaces'])) {
foreach ($props['@namespaces'] as $key => $namespace) {
$key = 'xmlns' . (($key) ? ':' . $key : '');
$attributes[$key] = $namespace;
}
}
// extract metadata from special array keys
$name = $props['@name'] ?? $name;
$attributes = $props['@attributes'] ?? [];
$value = $props['@value'] ?? null;
if (isset($props['@namespaces'])) {
foreach ($props['@namespaces'] as $key => $namespace) {
$key = 'xmlns' . (($key) ? ':' . $key : '');
$attributes[$key] = $namespace;
}
}
// continue with just the children
unset($props['@name'], $props['@attributes'], $props['@namespaces'], $props['@value']);
// continue with just the children
unset($props['@name'], $props['@attributes'], $props['@namespaces'], $props['@value']);
if (count($props) > 0) {
// there are children, use them instead of the value
if (count($props) > 0) {
// there are children, use them instead of the value
$value = [];
foreach ($props as $childName => $childItem) {
// render the child, but don't include the indentation of the first line
$value[] = trim(static::create($childItem, $childName, false, $indent, $level + 1));
}
}
$value = [];
foreach ($props as $childName => $childItem) {
// render the child, but don't include the indentation of the first line
$value[] = trim(static::create($childItem, $childName, false, $indent, $level + 1));
}
}
$result = static::tag($name, $value, $attributes, $indent, $level);
} else {
// just children
$result = static::tag($name, $value, $attributes, $indent, $level);
} else {
// just children
$result = [];
foreach ($props as $childItem) {
$result[] = static::create($childItem, $name, false, $indent, $level);
}
$result = [];
foreach ($props as $childItem) {
$result[] = static::create($childItem, $name, false, $indent, $level);
}
$result = implode(PHP_EOL, $result);
}
} else {
// scalar value
$result = implode(PHP_EOL, $result);
}
} else {
// scalar value
$result = static::tag($name, $props, null, $indent, $level);
}
$result = static::tag($name, $props, null, $indent, $level);
}
if ($head === true) {
return '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . $result;
} else {
return $result;
}
}
if ($head === true) {
return '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . $result;
} else {
return $result;
}
}
/**
* Removes all HTML/XML tags and encoded chars from a string
*
* ```
* echo Xml::decode('some &uuml;ber <em>crazy</em> stuff');
* // output: some über crazy stuff
* ```
*
* @param string|null $string
* @return string
*/
public static function decode(?string $string): string
{
if ($string === null) {
$string = '';
}
/**
* Removes all HTML/XML tags and encoded chars from a string
*
* ```
* echo Xml::decode('some &uuml;ber <em>crazy</em> stuff');
* // output: some über crazy stuff
* ```
*
* @param string|null $string
* @return string
*/
public static function decode(?string $string): string
{
if ($string === null) {
$string = '';
}
$string = strip_tags($string);
return html_entity_decode($string, ENT_COMPAT, 'utf-8');
}
$string = strip_tags($string);
return html_entity_decode($string, ENT_COMPAT, 'utf-8');
}
/**
* Converts a string to an XML-safe string
*
* Converts it to HTML-safe first and then it
* will replace HTML entities with XML entities
*
* ```php
* echo Xml::encode('some über crazy stuff');
* // output: some &#252;ber crazy stuff
* ```
*
* @param string|null $string
* @param bool $html True = Convert to HTML-safe first
* @return string
*/
public static function encode(?string $string, bool $html = true): string
{
if ($string === null) {
return '';
}
/**
* Converts a string to an XML-safe string
*
* Converts it to HTML-safe first and then it
* will replace HTML entities with XML entities
*
* ```php
* echo Xml::encode('some über crazy stuff');
* // output: some &#252;ber crazy stuff
* ```
*
* @param string|null $string
* @param bool $html True = Convert to HTML-safe first
* @return string
*/
public static function encode(?string $string, bool $html = true): string
{
if ($string === null) {
return '';
}
if ($html === true) {
$string = Html::encode($string, false);
}
if ($html === true) {
$string = Html::encode($string, false);
}
$entities = self::entities();
$html = array_keys($entities);
$xml = array_values($entities);
$entities = self::entities();
$html = array_keys($entities);
$xml = array_values($entities);
return str_replace($html, $xml, $string);
}
return str_replace($html, $xml, $string);
}
/**
* Returns the HTML-to-XML entity translation table
*
* @return array
*/
public static function entities(): array
{
return self::$entities;
}
/**
* Returns the HTML-to-XML entity translation table
*
* @return array
*/
public static function entities(): array
{
return self::$entities;
}
/**
* Parses an XML string and returns an array
*
* @param string $xml
* @return array|null Parsed array or `null` on error
*/
public static function parse(string $xml): ?array
{
$xml = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
/**
* Parses an XML string and returns an array
*
* @param string $xml
* @return array|null Parsed array or `null` on error
*/
public static function parse(string $xml): ?array
{
$xml = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
if (is_object($xml) !== true) {
return null;
}
if (is_object($xml) !== true) {
return null;
}
return static::simplify($xml);
}
return static::simplify($xml);
}
/**
* Breaks a SimpleXMLElement down into a simpler tree
* structure of arrays and strings
*
* @param \SimpleXMLElement $element
* @param bool $collectName Whether the element name should be collected (for the root element)
* @return array|string
*/
public static function simplify(SimpleXMLElement $element, bool $collectName = true)
{
// get all XML namespaces of the whole document to iterate over later;
// we don't need the global namespace (empty string) in the list
$usedNamespaces = $element->getNamespaces(true);
if (isset($usedNamespaces[''])) {
unset($usedNamespaces['']);
}
/**
* Breaks a SimpleXMLElement down into a simpler tree
* structure of arrays and strings
*
* @param \SimpleXMLElement $element
* @param bool $collectName Whether the element name should be collected (for the root element)
* @return array|string
*/
public static function simplify(SimpleXMLElement $element, bool $collectName = true)
{
// get all XML namespaces of the whole document to iterate over later;
// we don't need the global namespace (empty string) in the list
$usedNamespaces = $element->getNamespaces(true);
if (isset($usedNamespaces[''])) {
unset($usedNamespaces['']);
}
// now collect element metadata of the parent
$array = [];
if ($collectName === true) {
$array['@name'] = $element->getName();
}
// now collect element metadata of the parent
$array = [];
if ($collectName === true) {
$array['@name'] = $element->getName();
}
// collect attributes with each defined document namespace;
// also check for attributes without any namespace
$attributeArray = [];
foreach (array_merge([0 => null], array_keys($usedNamespaces)) as $namespace) {
$prefix = ($namespace) ? $namespace . ':' : '';
$attributes = $element->attributes($namespace, true);
// collect attributes with each defined document namespace;
// also check for attributes without any namespace
$attributeArray = [];
foreach (array_merge([0 => null], array_keys($usedNamespaces)) as $namespace) {
$prefix = ($namespace) ? $namespace . ':' : '';
$attributes = $element->attributes($namespace, true);
foreach ($attributes as $key => $value) {
$attributeArray[$prefix . $key] = (string)$value;
}
}
if (count($attributeArray) > 0) {
$array['@attributes'] = $attributeArray;
}
foreach ($attributes as $key => $value) {
$attributeArray[$prefix . $key] = (string)$value;
}
}
if (count($attributeArray) > 0) {
$array['@attributes'] = $attributeArray;
}
// collect namespace definitions of this particular XML element
if ($namespaces = $element->getDocNamespaces(false, false)) {
$array['@namespaces'] = $namespaces;
}
// collect namespace definitions of this particular XML element
if ($namespaces = $element->getDocNamespaces(false, false)) {
$array['@namespaces'] = $namespaces;
}
// check for children with each defined document namespace;
// also check for children without any namespace
$hasChildren = false;
foreach (array_merge([0 => null], array_keys($usedNamespaces)) as $namespace) {
$prefix = ($namespace) ? $namespace . ':' : '';
$children = $element->children($namespace, true);
// check for children with each defined document namespace;
// also check for children without any namespace
$hasChildren = false;
foreach (array_merge([0 => null], array_keys($usedNamespaces)) as $namespace) {
$prefix = ($namespace) ? $namespace . ':' : '';
$children = $element->children($namespace, true);
if (count($children) > 0) {
// there are children, recursively simplify each one
$hasChildren = true;
if (count($children) > 0) {
// there are children, recursively simplify each one
$hasChildren = true;
// make a grouped collection of elements per element name
foreach ($children as $child) {
$array[$prefix . $child->getName()][] = static::simplify($child, false);
}
}
}
// make a grouped collection of elements per element name
foreach ($children as $child) {
$array[$prefix . $child->getName()][] = static::simplify($child, false);
}
}
}
if ($hasChildren === true) {
// there were children of any namespace
if ($hasChildren === true) {
// there were children of any namespace
// reduce elements where there is only one item
// of the respective type to a simple string;
// don't do anything with special `@` metadata keys
foreach ($array as $name => $item) {
if (substr($name, 0, 1) !== '@' && count($item) === 1) {
$array[$name] = $item[0];
}
}
// reduce elements where there is only one item
// of the respective type to a simple string;
// don't do anything with special `@` metadata keys
foreach ($array as $name => $item) {
if (substr($name, 0, 1) !== '@' && count($item) === 1) {
$array[$name] = $item[0];
}
}
return $array;
} else {
// we didn't find any XML children above, only use the string value
$element = (string)$element;
return $array;
} else {
// we didn't find any XML children above, only use the string value
$element = (string)$element;
if (count($array) > 0) {
$array['@value'] = $element;
if (count($array) > 0) {
$array['@value'] = $element;
return $array;
} else {
return $element;
}
}
}
return $array;
} else {
return $element;
}
}
}
/**
* Builds an XML tag
*
* @param string $name Tag name
* @param array|string|null $content Scalar value or array with multiple lines of content or `null` to
* generate a self-closing tag; pass an empty string to generate empty content
* @param array $attr An associative array with additional attributes for the tag
* @param string|null $indent Indentation string, defaults to two spaces or `null` for output on one line
* @param int $level Indentation level
* @return string The generated XML
*/
public static function tag(string $name, $content = '', array $attr = null, ?string $indent = null, int $level = 0): string
{
$attr = static::attr($attr);
$start = '<' . $name . ($attr ? ' ' . $attr : '') . '>';
$startShort = '<' . $name . ($attr ? ' ' . $attr : '') . static::$void;
$end = '</' . $name . '>';
$baseIndent = $indent ? str_repeat($indent, $level) : '';
/**
* Builds an XML tag
*
* @param string $name Tag name
* @param array|string|null $content Scalar value or array with multiple lines of content or `null` to
* generate a self-closing tag; pass an empty string to generate empty content
* @param array $attr An associative array with additional attributes for the tag
* @param string|null $indent Indentation string, defaults to two spaces or `null` for output on one line
* @param int $level Indentation level
* @return string The generated XML
*/
public static function tag(string $name, $content = '', array $attr = null, ?string $indent = null, int $level = 0): string
{
$attr = static::attr($attr);
$start = '<' . $name . ($attr ? ' ' . $attr : '') . '>';
$startShort = '<' . $name . ($attr ? ' ' . $attr : '') . static::$void;
$end = '</' . $name . '>';
$baseIndent = $indent ? str_repeat($indent, $level) : '';
if (is_array($content) === true) {
if (is_string($indent) === true) {
$xml = $baseIndent . $start . PHP_EOL;
foreach ($content as $line) {
$xml .= $baseIndent . $indent . $line . PHP_EOL;
}
$xml .= $baseIndent . $end;
} else {
$xml = $start . implode($content) . $end;
}
} elseif ($content === null) {
$xml = $baseIndent . $startShort;
} else {
$xml = $baseIndent . $start . static::value($content) . $end;
}
if (is_array($content) === true) {
if (is_string($indent) === true) {
$xml = $baseIndent . $start . PHP_EOL;
foreach ($content as $line) {
$xml .= $baseIndent . $indent . $line . PHP_EOL;
}
$xml .= $baseIndent . $end;
} else {
$xml = $start . implode($content) . $end;
}
} elseif ($content === null) {
$xml = $baseIndent . $startShort;
} else {
$xml = $baseIndent . $start . static::value($content) . $end;
}
return $xml;
}
return $xml;
}
/**
* Properly encodes tag contents
*
* @param mixed $value
* @return string|null
*/
public static function value($value): ?string
{
if ($value === true) {
return 'true';
}
/**
* Properly encodes tag contents
*
* @param mixed $value
* @return string|null
*/
public static function value($value): ?string
{
if ($value === true) {
return 'true';
}
if ($value === false) {
return 'false';
}
if ($value === false) {
return 'false';
}
if (is_numeric($value) === true) {
return (string)$value;
}
if (is_numeric($value) === true) {
return (string)$value;
}
if ($value === null || $value === '') {
return null;
}
if ($value === null || $value === '') {
return null;
}
if (Str::startsWith($value, '<![CDATA[') === true) {
return $value;
}
if (Str::startsWith($value, '<![CDATA[') === true) {
return $value;
}
$encoded = htmlentities($value, ENT_NOQUOTES | ENT_XML1);
if ($encoded === $value) {
// no CDATA block needed
return $value;
}
$encoded = htmlentities($value, ENT_NOQUOTES | ENT_XML1);
if ($encoded === $value) {
// no CDATA block needed
return $value;
}
// wrap everything in a CDATA block
// and ensure that it is not closed in the input string
return '<![CDATA[' . str_replace(']]>', ']]]]><![CDATA[>', $value) . ']]>';
}
// wrap everything in a CDATA block
// and ensure that it is not closed in the input string
return '<![CDATA[' . str_replace(']]>', ']]]]><![CDATA[>', $value) . ']]>';
}
}