3.4.0
This commit is contained in:
@@ -81,29 +81,38 @@ class A
|
||||
return $array[$key];
|
||||
}
|
||||
|
||||
// support dot notation
|
||||
// extract data from nested array structures using the dot notation
|
||||
if (strpos($key, '.') !== false) {
|
||||
$keys = explode('.', $key);
|
||||
$firstKey = array_shift($keys);
|
||||
|
||||
// if the input array also uses dot notation, try to find a subset of the $keys
|
||||
if (isset($array[$firstKey]) === false) {
|
||||
$currentKey = $firstKey;
|
||||
|
||||
while ($innerKey = array_shift($keys)) {
|
||||
$currentKey = $currentKey . '.' . $innerKey;
|
||||
$currentKey .= '.' . $innerKey;
|
||||
|
||||
if (isset($array[$currentKey]) === true && is_array($array[$currentKey])) {
|
||||
// the element needs to exist and also needs to be an array; otherwise
|
||||
// we cannot find the remaining keys within it (invalid array structure)
|
||||
if (isset($array[$currentKey]) === true && is_array($array[$currentKey]) === true) {
|
||||
// $keys only holds the remaining keys that have not been shifted off yet
|
||||
return static::get($array[$currentKey], implode('.', $keys), $default);
|
||||
}
|
||||
}
|
||||
|
||||
// searching through the full chain of keys wasn't successful
|
||||
return $default;
|
||||
}
|
||||
|
||||
// if the input array uses a completely nested structure,
|
||||
// recursively progress layer by layer
|
||||
if (is_array($array[$firstKey]) === true) {
|
||||
return static::get($array[$firstKey], implode('.', $keys), $default);
|
||||
}
|
||||
|
||||
// the $firstKey element was found, but isn't an array, so we cannot
|
||||
// find the remaining keys within it (invalid array structure)
|
||||
return $default;
|
||||
}
|
||||
|
||||
@@ -410,6 +419,89 @@ class A
|
||||
return $missing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an array into a nested form by converting
|
||||
* dot notation in keys to nested structures
|
||||
*
|
||||
* @param array $array
|
||||
* @param array $ignore List of keys in dot notation that should
|
||||
* not be converted to a nested structure
|
||||
* @return array
|
||||
*/
|
||||
public static function nest(array $array, array $ignore = []): array
|
||||
{
|
||||
// convert a simple ignore list to a nested $key => true array
|
||||
if (isset($ignore[0]) === true) {
|
||||
$ignore = array_map(function () {
|
||||
return true;
|
||||
}, array_flip($ignore));
|
||||
|
||||
$ignore = A::nest($ignore);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($array as $fullKey => $value) {
|
||||
// extract the first part of a multi-level key, keep the others
|
||||
$subKeys = explode('.', $fullKey);
|
||||
$key = array_shift($subKeys);
|
||||
|
||||
// skip the magic for ignored keys
|
||||
if (isset($ignore[$key]) === true && $ignore[$key] === true) {
|
||||
$result[$fullKey] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// untangle elements where the key uses dot notation
|
||||
if (count($subKeys) > 0) {
|
||||
$value = static::nestByKeys($value, $subKeys);
|
||||
}
|
||||
|
||||
// now recursively do the same for each array level if needed
|
||||
if (is_array($value) === true) {
|
||||
$value = static::nest($value, $ignore[$key] ?? []);
|
||||
}
|
||||
|
||||
// merge arrays with previous results if necessary
|
||||
// (needed when the same keys are used both with and without dot notation)
|
||||
if (
|
||||
isset($result[$key]) === true &&
|
||||
is_array($result[$key]) === true &&
|
||||
is_array($value) === true
|
||||
) {
|
||||
$result[$key] = array_replace_recursive($result[$key], $value);
|
||||
} else {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively creates a nested array from a set of keys
|
||||
* with a key on each level
|
||||
*
|
||||
* @param mixed $value Arbitrary value that will end up at the bottom of the tree
|
||||
* @param array $keys List of keys to use sorted from the topmost level
|
||||
* @return array|mixed Nested array or (if `$keys` is empty) the input `$value`
|
||||
*/
|
||||
public static function nestByKeys($value, array $keys)
|
||||
{
|
||||
// shift off the first key from the list
|
||||
$firstKey = array_shift($keys);
|
||||
|
||||
// stop further recursion if there are no more keys
|
||||
if ($firstKey === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// return one level of the output tree, recurse further
|
||||
return [
|
||||
$firstKey => static::nestByKeys($value, $keys)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts a multi-dimensional array by a certain column
|
||||
*
|
||||
|
@@ -26,6 +26,14 @@ class Collection extends Iterator implements Countable
|
||||
*/
|
||||
public static $filters = [];
|
||||
|
||||
/**
|
||||
* Whether the collection keys should be
|
||||
* treated as case-sensitive
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $caseSensitive = false;
|
||||
|
||||
/**
|
||||
* Pagination object
|
||||
* @var Pagination
|
||||
@@ -48,9 +56,12 @@ class Collection extends Iterator implements Countable
|
||||
* Constructor
|
||||
*
|
||||
* @param array $data
|
||||
* @param bool $caseSensitive Whether the collection keys should be
|
||||
* treated as case-sensitive
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
public function __construct(array $data = [], bool $caseSensitive = false)
|
||||
{
|
||||
$this->caseSensitive = $caseSensitive;
|
||||
$this->set($data);
|
||||
}
|
||||
|
||||
@@ -72,11 +83,11 @@ class Collection extends Iterator implements Countable
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
if (isset($this->data[$key])) {
|
||||
return $this->data[$key];
|
||||
if ($this->caseSensitive === true) {
|
||||
return $this->data[$key] ?? null;
|
||||
}
|
||||
|
||||
return $this->data[strtolower($key)] ?? null;
|
||||
return $this->data[$key] ?? $this->data[strtolower($key)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +98,12 @@ class Collection extends Iterator implements Countable
|
||||
*/
|
||||
public function __set(string $key, $value)
|
||||
{
|
||||
$this->data[strtolower($key)] = $value;
|
||||
if ($this->caseSensitive === true) {
|
||||
$this->data[$key] = $value;
|
||||
} else {
|
||||
$this->data[strtolower($key)] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -1243,7 +1259,7 @@ Collection::$filters['!^='] = [
|
||||
/**
|
||||
* Between Filter
|
||||
*/
|
||||
Collection::$filters['between'] = [
|
||||
Collection::$filters['between'] = Collection::$filters['..'] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::between($value, ...$test) === true;
|
||||
},
|
||||
@@ -1294,3 +1310,67 @@ Collection::$filters['maxwords'] = [
|
||||
Collection::$filters['minwords'] = [
|
||||
'validator' => 'V::minWords',
|
||||
];
|
||||
|
||||
/**
|
||||
* Date Equals Filter
|
||||
*/
|
||||
Collection::$filters['date =='] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::date($value, '==', $test);
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Date Not Equals Filter
|
||||
*/
|
||||
Collection::$filters['date !='] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::date($value, '!=', $test);
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Date More Filter
|
||||
*/
|
||||
Collection::$filters['date >'] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::date($value, '>', $test);
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Date Min Filter
|
||||
*/
|
||||
Collection::$filters['date >='] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::date($value, '>=', $test);
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Date Less Filter
|
||||
*/
|
||||
Collection::$filters['date <'] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::date($value, '<', $test);
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Date Max Filter
|
||||
*/
|
||||
Collection::$filters['date <='] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::date($value, '<=', $test);
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Date Between Filter
|
||||
*/
|
||||
Collection::$filters['date between'] = Collection::$filters['date ..'] = [
|
||||
'validator' => function ($value, $test) {
|
||||
return V::date($value, '>=', $test[0]) &&
|
||||
V::date($value, '<=', $test[1]);
|
||||
}
|
||||
];
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use ArgumentCountError;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use TypeError;
|
||||
|
||||
@@ -224,8 +225,12 @@ class Component
|
||||
$definition = static::$types[$type];
|
||||
|
||||
// load definitions from string
|
||||
if (is_array($definition) === false) {
|
||||
static::$types[$type] = $definition = include $definition;
|
||||
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);
|
||||
}
|
||||
|
||||
return $definition;
|
||||
|
@@ -51,11 +51,11 @@ class Controller
|
||||
|
||||
public static function load(string $file)
|
||||
{
|
||||
if (file_exists($file) === false) {
|
||||
if (is_file($file) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$function = require $file;
|
||||
$function = F::load($file);
|
||||
|
||||
if (is_a($function, 'Closure') === false) {
|
||||
return null;
|
||||
|
@@ -99,7 +99,7 @@ class Dir
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the directory exsits on disk
|
||||
* Checks if the directory exists on disk
|
||||
*
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
|
@@ -103,7 +103,7 @@ class F
|
||||
],
|
||||
];
|
||||
|
||||
public static $units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
public static $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
/**
|
||||
* Appends new content to an existing file
|
||||
@@ -357,19 +357,24 @@ class F
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file and returns the result
|
||||
* Loads a file and returns the result or `false` if the
|
||||
* file to load does not exist
|
||||
*
|
||||
* @param string $file
|
||||
* @param mixed $fallback
|
||||
* @param array $data Optional array of variables to extract in the variable scope
|
||||
* @return mixed
|
||||
*/
|
||||
public static function load(string $file, $fallback = null)
|
||||
public static function load(string $file, $fallback = null, array $data = [])
|
||||
{
|
||||
if (file_exists($file) === false) {
|
||||
if (is_file($file) === false) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
$result = include $file;
|
||||
// we use the loadIsolated() method here to prevent the included
|
||||
// file from overwriting our $fallback in this variable scope; see
|
||||
// https://www.php.net/manual/en/function.include.php#example-124
|
||||
$result = static::loadIsolated($file, $data);
|
||||
|
||||
if ($fallback !== null && gettype($result) !== gettype($fallback)) {
|
||||
return $fallback;
|
||||
@@ -378,6 +383,39 @@ class F
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file with as little as possible in the variable scope
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $data Optional array of variables to extract in the variable scope
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function loadIsolated(string $file, array $data = [])
|
||||
{
|
||||
// extract the $data variables in this scope to be accessed by the included file;
|
||||
// protect $file against being overwritten by a $data variable
|
||||
$___file___ = $file;
|
||||
extract($data);
|
||||
|
||||
return include $___file___;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file using `include_once()` and returns whether loading was successful
|
||||
*
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
public static function loadOnce(string $file): bool
|
||||
{
|
||||
if (is_file($file) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
include_once $file;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime type of a file
|
||||
*
|
||||
@@ -498,7 +536,7 @@ class F
|
||||
|
||||
// avoid errors for invalid sizes
|
||||
if ($size <= 0) {
|
||||
return '0 kB';
|
||||
return '0 KB';
|
||||
}
|
||||
|
||||
// the math magic
|
||||
|
@@ -6,7 +6,7 @@ use Exception;
|
||||
use Kirby\Http\Url;
|
||||
|
||||
/**
|
||||
* Html builder for the most common elements
|
||||
* HTML builder for the most common elements
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
@@ -14,58 +14,60 @@ use Kirby\Http\Url;
|
||||
* @copyright Bastian Allgeier GmbH
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
class Html
|
||||
class Html extends Xml
|
||||
{
|
||||
/**
|
||||
* An internal store for a html entities translation table
|
||||
* An internal store for an HTML entities translation table
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $entities;
|
||||
|
||||
/**
|
||||
* Can be used to switch to trailing slashes if required
|
||||
* Closing string for void tags;
|
||||
* can be used to switch to trailing slashes if required
|
||||
*
|
||||
* ```php
|
||||
* html::$void = ' />'
|
||||
* Html::$void = ' />'
|
||||
* ```
|
||||
*
|
||||
* @var string $void
|
||||
* @var string
|
||||
*/
|
||||
public static $void = '>';
|
||||
|
||||
/**
|
||||
* Generic HTML tag generator
|
||||
* Can be called like `Html::p('A paragraph', ['class' => 'text'])`
|
||||
*
|
||||
* @param string $tag
|
||||
* @param array $arguments
|
||||
* @param string $tag Tag name
|
||||
* @param array $arguments Further arguments for the Html::tag() method
|
||||
* @return string
|
||||
*/
|
||||
public static function __callStatic(string $tag, array $arguments = []): string
|
||||
{
|
||||
if (static::isVoid($tag) === true) {
|
||||
return Html::tag($tag, null, ...$arguments);
|
||||
return static::tag($tag, null, ...$arguments);
|
||||
}
|
||||
|
||||
return Html::tag($tag, ...$arguments);
|
||||
return static::tag($tag, ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an `a` tag
|
||||
* Generates an `<a>` tag; automatically supports mailto: and tel: links
|
||||
*
|
||||
* @param string $href The url for the `a` tag
|
||||
* @param mixed $text The optional text. If `null`, the url will be used as text
|
||||
* @param string $href The URL for the `<a>` tag
|
||||
* @param string|array|null $text The optional text; if `null`, the URL will be used as text
|
||||
* @param array $attr Additional attributes for the tag
|
||||
* @return string the generated html
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function a(string $href = null, $text = null, array $attr = []): string
|
||||
public static function a(string $href, $text = null, array $attr = []): string
|
||||
{
|
||||
if (Str::startsWith($href, 'mailto:')) {
|
||||
return static::email($href, $text, $attr);
|
||||
return static::email(substr($href, 7), $text, $attr);
|
||||
}
|
||||
|
||||
if (Str::startsWith($href, 'tel:')) {
|
||||
return static::tel($href, $text, $attr);
|
||||
return static::tel(substr($href, 4), $text, $attr);
|
||||
}
|
||||
|
||||
return static::link($href, $text, $attr);
|
||||
@@ -74,92 +76,47 @@ class Html
|
||||
/**
|
||||
* Generates a single attribute or a list of attributes
|
||||
*
|
||||
* @param string $name mixed string: a single attribute with that name will be generated. array: a list of attributes will be generated. Don't pass a second argument in that case.
|
||||
* @param string $value if used for a single attribute, pass the content for the attribute here
|
||||
* @return string the generated html
|
||||
* @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 HTML attributes string
|
||||
*/
|
||||
public static function attr($name, $value = null): string
|
||||
public static function attr($name, $value = null): ?string
|
||||
{
|
||||
if (is_array($name) === true) {
|
||||
$attributes = [];
|
||||
|
||||
ksort($name);
|
||||
|
||||
foreach ($name as $key => $val) {
|
||||
$a = static::attr($key, $val);
|
||||
|
||||
if ($a) {
|
||||
$attributes[] = $a;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(' ', $attributes);
|
||||
// HTML supports boolean attributes without values
|
||||
if (is_array($name) === false && is_bool($value) === true) {
|
||||
return $value === true ? strtolower($name) : null;
|
||||
}
|
||||
|
||||
if ($value === null || $value === '' || $value === []) {
|
||||
return false;
|
||||
}
|
||||
// all other cases can share the XML variant
|
||||
$attr = parent::attr($name, $value);
|
||||
|
||||
if ($value === ' ') {
|
||||
return strtolower($name) . '=""';
|
||||
}
|
||||
|
||||
if (is_bool($value) === true) {
|
||||
return $value === true ? strtolower($name) : '';
|
||||
}
|
||||
|
||||
if (is_array($value) === true) {
|
||||
if (isset($value['value'], $value['escape'])) {
|
||||
$value = $value['escape'] === true ? htmlspecialchars($value['value'], ENT_QUOTES, 'UTF-8') : $value['value'];
|
||||
} else {
|
||||
$value = implode(' ', array_filter($value, function ($value) {
|
||||
return !empty($value) || is_numeric($value);
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
return strtolower($name) . '="' . $value . '"';
|
||||
// HTML supports named entities
|
||||
$entities = parent::entities();
|
||||
$html = array_keys($entities);
|
||||
$xml = array_values($entities);
|
||||
return str_replace($xml, $html, $attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts lines in a string into html breaks
|
||||
* Converts lines in a string into HTML breaks
|
||||
*
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public static function breaks(string $string = null): string
|
||||
public static function breaks(string $string): string
|
||||
{
|
||||
return nl2br($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all html tags and encoded chars from a string
|
||||
* Generates an `<a>` tag with `mailto:`
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
* echo html::decode('some uber <em>crazy</em> stuff');
|
||||
* // output: some uber crazy stuff
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
* @param string $string
|
||||
* @return string The html string
|
||||
*/
|
||||
public static function decode(string $string = null): string
|
||||
{
|
||||
$string = strip_tags($string);
|
||||
return html_entity_decode($string, ENT_COMPAT, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an `a` tag with `mailto:`
|
||||
*
|
||||
* @param string $email The url for the a tag
|
||||
* @param mixed $text The optional text. If null, the url will be used as text
|
||||
* @param string $email The email address
|
||||
* @param string|array|null $text The optional text; if `null`, the email address will be used as text
|
||||
* @param array $attr Additional attributes for the tag
|
||||
* @return string the generated html
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function email(string $email, $text = null, array $attr = []): string
|
||||
{
|
||||
@@ -168,8 +125,10 @@ class Html
|
||||
}
|
||||
|
||||
if (empty($text) === true) {
|
||||
// show only the eMail address without additional parameters (if the 'text' argument is empty)
|
||||
$text = [Str::encode(Str::split($email, '?')[0])];
|
||||
// show only the email address without additional parameters
|
||||
$address = Str::contains($email, '?') ? Str::before($email, '?') : $email;
|
||||
|
||||
$text = [Str::encode($address)];
|
||||
}
|
||||
|
||||
$email = Str::encode($email);
|
||||
@@ -187,14 +146,18 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to a html-safe string
|
||||
* Converts a string to an HTML-safe string
|
||||
*
|
||||
* @param string $string
|
||||
* @param bool $keepTags
|
||||
* @return string The html string
|
||||
* @param string|null $string
|
||||
* @param bool $keepTags If true, existing tags won't be escaped
|
||||
* @return string The HTML string
|
||||
*/
|
||||
public static function encode(string $string = null, bool $keepTags = false): string
|
||||
public static function encode(?string $string, bool $keepTags = false): string
|
||||
{
|
||||
if ($string === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($keepTags === true) {
|
||||
$list = static::entities();
|
||||
unset($list['"'], $list['<'], $list['>'], $list['&']);
|
||||
@@ -209,24 +172,24 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entities translation table
|
||||
* Returns the entity translation table
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function entities(): array
|
||||
{
|
||||
return static::$entities = static::$entities ?? get_html_translation_table(HTML_ENTITIES);
|
||||
return self::$entities = self::$entities ?? get_html_translation_table(HTML_ENTITIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a figure tag with optional caption
|
||||
* Creates a `<figure>` tag with optional caption
|
||||
*
|
||||
* @param string|array $content
|
||||
* @param string|array $caption
|
||||
* @param array $attr
|
||||
* @return string
|
||||
* @param string|array $content Contents of the `<figure>` tag
|
||||
* @param string|array $caption Optional `<figcaption>` text to use
|
||||
* @param array $attr Additional attributes for the `<figure>` tag
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function figure($content, $caption = null, array $attr = []): string
|
||||
public static function figure($content, $caption = '', array $attr = []): string
|
||||
{
|
||||
if ($caption) {
|
||||
$figcaption = static::tag('figcaption', $caption);
|
||||
@@ -242,14 +205,14 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a gist
|
||||
* Embeds a GitHub Gist
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $file
|
||||
* @param array $attr
|
||||
* @return string
|
||||
* @param string $url Gist URL
|
||||
* @param string|null $file Optional specific file to embed
|
||||
* @param array $attr Additional attributes for the `<script>` tag
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function gist(string $url, string $file = null, array $attr = []): string
|
||||
public static function gist(string $url, ?string $file = null, array $attr = []): string
|
||||
{
|
||||
if ($file === null) {
|
||||
$src = $url . '.js';
|
||||
@@ -257,29 +220,27 @@ class Html
|
||||
$src = $url . '.js?file=' . $file;
|
||||
}
|
||||
|
||||
return static::tag('script', null, array_merge($attr, [
|
||||
'src' => $src
|
||||
]));
|
||||
return static::tag('script', '', array_merge($attr, ['src' => $src]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an iframe
|
||||
* Creates an `<iframe>`
|
||||
*
|
||||
* @param string $src
|
||||
* @param array $attr
|
||||
* @return string
|
||||
* @param array $attr Additional attributes for the `<iframe>` tag
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function iframe(string $src, array $attr = []): string
|
||||
{
|
||||
return static::tag('iframe', null, array_merge(['src' => $src], $attr));
|
||||
return static::tag('iframe', '', array_merge(['src' => $src], $attr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an img tag
|
||||
* Generates an `<img>` tag
|
||||
*
|
||||
* @param string $src The url of the image
|
||||
* @param array $attr Additional attributes for the image tag
|
||||
* @return string the generated html
|
||||
* @param string $src The URL of the image
|
||||
* @param array $attr Additional attributes for the `<img>` tag
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function img(string $src, array $attr = []): string
|
||||
{
|
||||
@@ -288,7 +249,7 @@ class Html
|
||||
'alt' => ' '
|
||||
], $attr);
|
||||
|
||||
return static::tag('img', null, $attr);
|
||||
return static::tag('img', '', $attr);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,14 +283,14 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an `a` link tag
|
||||
* Generates an `<a>` link tag (without automatic email: and tel: detection)
|
||||
*
|
||||
* @param string $href The url for the `a` tag
|
||||
* @param mixed $text The optional text. If `null`, the url will be used as text
|
||||
* @param string $href The URL for the `<a>` tag
|
||||
* @param string|array|null $text The optional text; if `null`, the URL will be used as text
|
||||
* @param array $attr Additional attributes for the tag
|
||||
* @return string the generated html
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function link(string $href = null, $text = null, array $attr = []): string
|
||||
public static function link(string $href, $text = null, array $attr = []): string
|
||||
{
|
||||
$attr = array_merge(['href' => $href], $attr);
|
||||
|
||||
@@ -348,13 +309,13 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Add noopeener noreferrer to rels when target is `_blank`
|
||||
* Add noopener & noreferrer to rels when target is `_blank`
|
||||
*
|
||||
* @param string $rel
|
||||
* @param string $target
|
||||
* @return string|null
|
||||
* @param string|null $rel Current `rel` value
|
||||
* @param string|null $target Current `target` value
|
||||
* @return string|null New `rel` value or `null` if not needed
|
||||
*/
|
||||
public static function rel(string $rel = null, string $target = null)
|
||||
public static function rel(?string $rel = null, ?string $target = null): ?string
|
||||
{
|
||||
$rel = trim($rel);
|
||||
|
||||
@@ -370,47 +331,35 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an Html tag with optional content and attributes
|
||||
* Builds an HTML tag
|
||||
*
|
||||
* @param string $name The name of the tag, i.e. `a`
|
||||
* @param mixed $content The content if availble. Pass `null` to generate a self-closing tag, Pass an empty string to generate empty content
|
||||
* @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
|
||||
* @return string The generated Html
|
||||
* @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 HTML
|
||||
*/
|
||||
public static function tag(string $name, $content = null, array $attr = []): string
|
||||
public static function tag(string $name, $content = '', array $attr = null, string $indent = null, int $level = 0): string
|
||||
{
|
||||
$html = '<' . $name;
|
||||
$attr = static::attr($attr);
|
||||
|
||||
if (empty($attr) === false) {
|
||||
$html .= ' ' . $attr;
|
||||
}
|
||||
|
||||
// force void elements to be self-closing
|
||||
if (static::isVoid($name) === true) {
|
||||
$html .= static::$void;
|
||||
} else {
|
||||
if (is_array($content) === true) {
|
||||
$content = implode($content);
|
||||
} else {
|
||||
$content = static::encode($content, false);
|
||||
}
|
||||
|
||||
$html .= '>' . $content . '</' . $name . '>';
|
||||
$content = null;
|
||||
}
|
||||
|
||||
return $html;
|
||||
return parent::tag($name, $content, $attr, $indent, $level);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates an `a` tag for a phone number
|
||||
* Generates an `<a>` tag for a phone number
|
||||
*
|
||||
* @param string $tel The phone number
|
||||
* @param mixed $text The optional text. If `null`, the number will be used as text
|
||||
* @param string|array|null $text The optional text; if `null`, the phone number will be used as text
|
||||
* @param array $attr Additional attributes for the tag
|
||||
* @return string the generated html
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function tel($tel = null, $text = null, array $attr = []): string
|
||||
public static function tel(string $tel, $text = null, array $attr = []): string
|
||||
{
|
||||
$number = preg_replace('![^0-9\+]+!', '', $tel);
|
||||
|
||||
@@ -422,16 +371,44 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a video embed via iframe for Youtube or Vimeo
|
||||
* videos. The embed Urls are automatically detected from
|
||||
* the given URL.
|
||||
* Properly encodes tag contents
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $options
|
||||
* @param array $attr
|
||||
* @return string
|
||||
* @param mixed $value
|
||||
* @return string|null
|
||||
*/
|
||||
public static function video(string $url, ?array $options = [], array $attr = []): string
|
||||
public static function value($value): ?string
|
||||
{
|
||||
if ($value === true) {
|
||||
return 'true';
|
||||
}
|
||||
|
||||
if ($value === false) {
|
||||
return 'false';
|
||||
}
|
||||
|
||||
if (is_numeric($value) === true) {
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::encode($value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a video embed via `<iframe>` for YouTube or Vimeo
|
||||
* videos; the embed URLs are automatically detected from
|
||||
* the given URL
|
||||
*
|
||||
* @param string $url Video URL
|
||||
* @param array $options Additional `vimeo` and `youtube` options
|
||||
* (will be used as query params in the embed URL)
|
||||
* @param array $attr Additional attributes for the `<iframe>` tag
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function video(string $url, array $options = [], array $attr = []): string
|
||||
{
|
||||
// YouTube video
|
||||
if (preg_match('!youtu!i', $url) === 1) {
|
||||
@@ -447,14 +424,14 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a Vimeo video by URL in an iframe
|
||||
* Embeds a Vimeo video by URL in an `<iframe>`
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $options
|
||||
* @param array $attr
|
||||
* @return string
|
||||
* @param string $url Vimeo video URL
|
||||
* @param array $options Query params for the embed URL
|
||||
* @param array $attr Additional attributes for the `<iframe>` tag
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function vimeo(string $url, ?array $options = [], array $attr = []): string
|
||||
public static function vimeo(string $url, array $options = [], array $attr = []): string
|
||||
{
|
||||
if (preg_match('!vimeo.com\/([0-9]+)!i', $url, $array) === 1) {
|
||||
$id = $array[1];
|
||||
@@ -477,59 +454,106 @@ class Html
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a Youtube video by URL in an iframe
|
||||
* Embeds a YouTube video by URL in an `<iframe>`
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $options
|
||||
* @param array $attr
|
||||
* @return string
|
||||
* @param string $url YouTube video URL
|
||||
* @param array $options Query params for the embed URL
|
||||
* @param array $attr Additional attributes for the `<iframe>` tag
|
||||
* @return string The generated HTML
|
||||
*/
|
||||
public static function youtube(string $url, ?array $options = [], array $attr = []): string
|
||||
public static function youtube(string $url, array $options = [], array $attr = []): string
|
||||
{
|
||||
// youtube embed domain
|
||||
$domain = 'youtube.com';
|
||||
$id = null;
|
||||
// default YouTube embed domain
|
||||
$domain = 'youtube.com';
|
||||
$uri = 'embed/';
|
||||
$id = null;
|
||||
$urlOptions = [];
|
||||
|
||||
$schemes = [
|
||||
// http://www.youtube.com/embed/d9NF2edxy-M
|
||||
['pattern' => 'youtube.com\/embed\/([a-zA-Z0-9_-]+)'],
|
||||
// https://www.youtube.com/embed/videoseries?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube.com\/embed\/videoseries\?list=([a-zA-Z0-9_-]+)',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
|
||||
// https://www.youtube-nocookie.com/embed/videoseries?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/embed\/videoseries\?list=([a-zA-Z0-9_-]+)',
|
||||
'domain' => 'www.youtube-nocookie.com',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
|
||||
// https://www.youtube.com/embed/d9NF2edxy-M
|
||||
// https://www.youtube.com/embed/d9NF2edxy-M?start=10
|
||||
['pattern' => 'youtube.com\/embed\/([a-zA-Z0-9_-]+)(?:\?(.+))?'],
|
||||
|
||||
// https://www.youtube-nocookie.com/embed/d9NF2edxy-M
|
||||
// https://www.youtube-nocookie.com/embed/d9NF2edxy-M?start=10
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/embed\/([a-zA-Z0-9_-]+)',
|
||||
'pattern' => 'youtube-nocookie.com\/embed\/([a-zA-Z0-9_-]+)(?:\?(.+))?',
|
||||
'domain' => 'www.youtube-nocookie.com'
|
||||
],
|
||||
|
||||
// https://www.youtube-nocookie.com/watch?v=d9NF2edxy-M
|
||||
// https://www.youtube-nocookie.com/watch?v=d9NF2edxy-M&t=10
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/watch\?v=([a-zA-Z0-9_-]+)',
|
||||
'pattern' => 'youtube-nocookie.com\/watch\?v=([a-zA-Z0-9_-]+)(?:&(.+))?',
|
||||
'domain' => 'www.youtube-nocookie.com'
|
||||
],
|
||||
// http://www.youtube.com/watch?v=d9NF2edxy-M
|
||||
['pattern' => 'v=([a-zA-Z0-9_-]+)'],
|
||||
// http://youtu.be/d9NF2edxy-M
|
||||
['pattern' => 'youtu.be\/([a-zA-Z0-9_-]+)']
|
||||
|
||||
// https://www.youtube-nocookie.com/playlist?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube-nocookie.com\/playlist\?list=([a-zA-Z0-9_-]+)',
|
||||
'domain' => 'www.youtube-nocookie.com',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
|
||||
// https://www.youtube.com/watch?v=d9NF2edxy-M
|
||||
// https://www.youtube.com/watch?v=d9NF2edxy-M&t=10
|
||||
['pattern' => 'youtube.com\/watch\?v=([a-zA-Z0-9_-]+)(?:&(.+))?'],
|
||||
|
||||
// https://www.youtube.com/playlist?list=PLj8e95eaxiB9goOAvINIy4Vt3mlWQJxys
|
||||
[
|
||||
'pattern' => 'youtube.com\/playlist\?list=([a-zA-Z0-9_-]+)',
|
||||
'uri' => 'embed/videoseries?list='
|
||||
],
|
||||
|
||||
// https://youtu.be/d9NF2edxy-M
|
||||
// https://youtu.be/d9NF2edxy-M?t=10
|
||||
['pattern' => 'youtu.be\/([a-zA-Z0-9_-]+)(?:\?(.+))?']
|
||||
];
|
||||
|
||||
foreach ($schemes as $schema) {
|
||||
if (preg_match('!' . $schema['pattern'] . '!i', $url, $array) === 1) {
|
||||
$domain = $schema['domain'] ?? $domain;
|
||||
$uri = $schema['uri'] ?? $uri;
|
||||
$id = $array[1];
|
||||
if (isset($array[2]) === true) {
|
||||
parse_str($array[2], $urlOptions);
|
||||
|
||||
// convert video URL options to embed URL options
|
||||
if (isset($urlOptions['t']) === true) {
|
||||
$urlOptions['start'] = $urlOptions['t'];
|
||||
unset($urlOptions['t']);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no match
|
||||
if ($id === null) {
|
||||
throw new Exception('Invalid Youtube source');
|
||||
throw new Exception('Invalid YouTube source');
|
||||
}
|
||||
|
||||
// build the options query
|
||||
if (empty($options) === false) {
|
||||
$query = '?' . http_build_query($options);
|
||||
if (empty($options) === false || empty($urlOptions) === false) {
|
||||
$query = (Str::contains($uri, '?') === true ? '&' : '?') . http_build_query(array_merge($urlOptions, $options));
|
||||
} else {
|
||||
$query = '';
|
||||
}
|
||||
|
||||
$url = 'https://' . $domain . '/embed/' . $id . $query;
|
||||
$url = 'https://' . $domain . '/' . $uri . $id . $query;
|
||||
|
||||
return static::iframe($url, array_merge(['allowfullscreen' => true], $attr));
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Localization class, roughly inspired by VueI18n
|
||||
@@ -191,7 +190,14 @@ class I18n
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate amounts
|
||||
* 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
|
||||
@@ -206,23 +212,18 @@ class I18n
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_a($translation, 'Closure') === true) {
|
||||
return $translation($count);
|
||||
}
|
||||
|
||||
if (is_string($translation) === true) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
if (count($translation) !== 3) {
|
||||
throw new Exception('Please provide 3 translations');
|
||||
}
|
||||
|
||||
switch ($count) {
|
||||
case 0:
|
||||
$message = $translation[0];
|
||||
break;
|
||||
case 1:
|
||||
$message = $translation[1];
|
||||
break;
|
||||
default:
|
||||
$message = $translation[2];
|
||||
$message = $translation;
|
||||
} else {
|
||||
if (isset($translation[$count]) === true) {
|
||||
$message = $translation[$count];
|
||||
} else {
|
||||
$message = end($translation);
|
||||
}
|
||||
}
|
||||
|
||||
return str_replace('{{ count }}', $count, $message);
|
||||
|
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Kirby\Exception\BadMethodCallException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* The Query class can be used to
|
||||
* query arrays and objects, including their
|
||||
@@ -15,14 +18,13 @@ namespace Kirby\Toolkit;
|
||||
*/
|
||||
class Query
|
||||
{
|
||||
const PARTS = '!([a-zA-Z_][a-zA-Z0-9_]*(\(.*?\))?)\.|' . self::SKIP . '!';
|
||||
const METHOD = '!\((.*)\)!';
|
||||
const PARAMETERS = '!,|' . self::SKIP . '!';
|
||||
const PARTS = '!\.|(\(([^()]+|(?1))*+\))(*SKIP)(*FAIL)!'; // split by dot, but not inside (nested) parens
|
||||
const PARAMETERS = '!,|' . self::SKIP . '!'; // split by comma, but not inside skip groups
|
||||
|
||||
const NO_PNTH = '\([^\(]+\)(*SKIP)(*FAIL)';
|
||||
const NO_PNTH = '\([^(]+\)(*SKIP)(*FAIL)';
|
||||
const NO_SQBR = '\[[^]]+\](*SKIP)(*FAIL)';
|
||||
const NO_DLQU = '\"[^"]+\"(*SKIP)(*FAIL)';
|
||||
const NO_SLQU = '\'[^\']+\'(*SKIP)(*FAIL)';
|
||||
const NO_DLQU = '\"(?:[^"\\\\]|\\\\.)*\"(*SKIP)(*FAIL)'; // allow \" escaping inside string
|
||||
const NO_SLQU = '\'(?:[^\'\\\\]|\\\\.)*\'(*SKIP)(*FAIL)'; // allow \' escaping inside string
|
||||
const SKIP = self::NO_PNTH . '|' . self::NO_SQBR . '|' .
|
||||
self::NO_DLQU . '|' . self::NO_SLQU;
|
||||
|
||||
@@ -43,10 +45,10 @@ class Query
|
||||
/**
|
||||
* Creates a new Query object
|
||||
*
|
||||
* @param string $query
|
||||
* @param string|null $query
|
||||
* @param array|object $data
|
||||
*/
|
||||
public function __construct(string $query = null, $data = [])
|
||||
public function __construct(?string $query = null, $data = [])
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->data = $data;
|
||||
@@ -54,7 +56,7 @@ class Query
|
||||
|
||||
/**
|
||||
* Returns the query result if anything
|
||||
* can be found. Otherwise returns null.
|
||||
* can be found, otherwise returns null
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -69,10 +71,12 @@ class Query
|
||||
|
||||
/**
|
||||
* Resolves the query if anything
|
||||
* can be found. Otherwise returns null.
|
||||
* 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)
|
||||
{
|
||||
@@ -85,27 +89,47 @@ class Query
|
||||
$data = $this->data;
|
||||
$value = null;
|
||||
|
||||
while (count($parts)) {
|
||||
$part = array_shift($parts);
|
||||
foreach ($parts as $part) {
|
||||
$info = $this->part($part);
|
||||
$method = $info['method'];
|
||||
$value = null;
|
||||
$args = $info['args'];
|
||||
|
||||
if (is_array($data)) {
|
||||
$value = $data[$method] ?? null;
|
||||
} elseif (is_object($data)) {
|
||||
if (method_exists($data, $method) || method_exists($data, '__call')) {
|
||||
$value = $data->$method(...$info['args']);
|
||||
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);
|
||||
}
|
||||
} elseif (is_scalar($data)) {
|
||||
return $data;
|
||||
} else {
|
||||
return null;
|
||||
// further parts on a scalar/null value
|
||||
static::accessError($data, $method, 'method/property');
|
||||
}
|
||||
|
||||
if (is_array($value) || is_object($value)) {
|
||||
$data = $value;
|
||||
}
|
||||
// continue with the current value for the next part
|
||||
$data = $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
@@ -119,60 +143,54 @@ class Query
|
||||
*/
|
||||
protected function parts(string $query): array
|
||||
{
|
||||
$query = trim($query);
|
||||
|
||||
// match all parts but the last
|
||||
preg_match_all(self::PARTS, $query, $match);
|
||||
|
||||
// remove all matched parts from the query to retrieve last part
|
||||
foreach ($match[0] as $part) {
|
||||
$query = Str::after($query, $part);
|
||||
}
|
||||
|
||||
array_push($match[1], $query);
|
||||
return $match[1];
|
||||
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.
|
||||
* extracts methods and method arguments
|
||||
*
|
||||
* @param string $part
|
||||
* @return array
|
||||
*/
|
||||
protected function part(string $part): array
|
||||
{
|
||||
$args = [];
|
||||
$method = preg_replace_callback(self::METHOD, function ($match) use (&$args) {
|
||||
$args = preg_split(self::PARAMETERS, $match[1]);
|
||||
$args = array_map('self::parameter', $args);
|
||||
}, $part);
|
||||
if (Str::endsWith($part, ')') === true) {
|
||||
$method = Str::before($part, '(');
|
||||
|
||||
return [
|
||||
'method' => $method,
|
||||
'args' => $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' => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a parameter of query to
|
||||
* proper type.
|
||||
* Converts a parameter of a query to
|
||||
* its proper native PHP type
|
||||
*
|
||||
* @param mixed $arg
|
||||
* @param string $arg
|
||||
* @return mixed
|
||||
*/
|
||||
protected function parameter($arg)
|
||||
protected function parameter(string $arg)
|
||||
{
|
||||
$arg = trim($arg);
|
||||
|
||||
// string with double quotes
|
||||
if (substr($arg, 0, 1) === '"') {
|
||||
return trim($arg, '"');
|
||||
if (substr($arg, 0, 1) === '"' && substr($arg, -1) === '"') {
|
||||
return str_replace('\"', '"', substr($arg, 1, -1));
|
||||
}
|
||||
|
||||
// string with single quotes
|
||||
if (substr($arg, 0, 1) === '\'') {
|
||||
return trim($arg, '\'');
|
||||
if (substr($arg, 0, 1) === "'" && substr($arg, -1) === "'") {
|
||||
return str_replace("\'", "'", substr($arg, 1, -1));
|
||||
}
|
||||
|
||||
// boolean or null
|
||||
@@ -200,4 +218,27 @@ class Query
|
||||
// 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';
|
||||
}
|
||||
|
||||
$nonExisting = in_array($type, ['array', 'object']) ? 'non-existing ' : '';
|
||||
|
||||
$error = 'Access to ' . $nonExisting . $label . ' ' . $name . ' on ' . $type;
|
||||
throw new BadMethodCallException($error);
|
||||
}
|
||||
}
|
||||
|
@@ -341,7 +341,7 @@ class Str
|
||||
* @param string $rep The element, which should be added if the string is too long. Ellipsis is the default.
|
||||
* @return string The shortened string
|
||||
*/
|
||||
public static function excerpt($string, $chars = 140, $strip = true, $rep = '…')
|
||||
public static function excerpt($string, $chars = 140, $strip = true, $rep = ' …')
|
||||
{
|
||||
if ($strip === true) {
|
||||
$string = strip_tags(str_replace('<', ' <', $string));
|
||||
@@ -361,7 +361,7 @@ class Str
|
||||
return $string;
|
||||
}
|
||||
|
||||
return static::substr($string, 0, mb_strrpos(static::substr($string, 0, $chars), ' ')) . ' ' . $rep;
|
||||
return static::substr($string, 0, mb_strrpos(static::substr($string, 0, $chars), ' ')) . $rep;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -897,10 +897,25 @@ class Str
|
||||
{
|
||||
return preg_replace_callback('!' . $start . '(.*?)' . $end . '!', function ($match) use ($data, $fallback) {
|
||||
$query = trim($match[1]);
|
||||
|
||||
// if the placeholder contains a dot, it is a query
|
||||
if (strpos($query, '.') !== false) {
|
||||
return (new Query($match[1], $data))->result() ?? $fallback;
|
||||
try {
|
||||
$result = (new Query($match[1], $data))->result();
|
||||
} catch (Exception $e) {
|
||||
$result = null;
|
||||
}
|
||||
} else {
|
||||
$result = $data[$query] ?? null;
|
||||
}
|
||||
return $data[$query] ?? $fallback;
|
||||
|
||||
// if we don't have a result, use the fallback if given
|
||||
if ($result === null && $fallback !== null) {
|
||||
$result = $fallback;
|
||||
}
|
||||
|
||||
// if we still don't have a result, keep the original placeholder
|
||||
return $result ?? $match[0];
|
||||
}, $string);
|
||||
}
|
||||
|
||||
|
@@ -18,23 +18,21 @@ class Tpl
|
||||
/**
|
||||
* Renders the template
|
||||
*
|
||||
* @param string $__file
|
||||
* @param array $__data
|
||||
* @param string $file
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public static function load(string $__file = null, array $__data = []): string
|
||||
public static function load(string $file = null, array $data = []): string
|
||||
{
|
||||
if (file_exists($__file) === false) {
|
||||
if (is_file($file) === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$exception = null;
|
||||
|
||||
ob_start();
|
||||
extract($__data);
|
||||
|
||||
$exception = null;
|
||||
try {
|
||||
require $__file;
|
||||
F::load($file, null, $data);
|
||||
} catch (Throwable $e) {
|
||||
$exception = $e;
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Http\Idn;
|
||||
use ReflectionFunction;
|
||||
use Throwable;
|
||||
@@ -239,13 +240,47 @@ V::$validators = [
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks for a valid date
|
||||
* Checks for a valid date or compares two
|
||||
* dates with each other.
|
||||
*
|
||||
* Pass only the first argument to check for a valid date.
|
||||
* Pass an operator as second argument and another date as
|
||||
* third argument to compare them.
|
||||
*/
|
||||
'date' => function ($value): bool {
|
||||
$date = date_parse($value);
|
||||
return $date !== false &&
|
||||
$date['error_count'] === 0 &&
|
||||
$date['warning_count'] === 0;
|
||||
'date' => function (?string $value, string $operator = null, string $test = null): bool {
|
||||
$args = func_get_args();
|
||||
|
||||
// simple date validation
|
||||
if (count($args) === 1) {
|
||||
$date = date_parse($value);
|
||||
return $date !== false &&
|
||||
$date['error_count'] === 0 &&
|
||||
$date['warning_count'] === 0;
|
||||
}
|
||||
|
||||
$value = strtotime($value);
|
||||
$test = strtotime($test);
|
||||
|
||||
if (is_int($value) !== true || is_int($test) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($operator) {
|
||||
case '!=':
|
||||
return $value !== $test;
|
||||
case '<':
|
||||
return $value < $test;
|
||||
case '>':
|
||||
return $value > $test;
|
||||
case '<=':
|
||||
return $value <= $test;
|
||||
case '>=':
|
||||
return $value >= $test;
|
||||
case '==':
|
||||
return $value === $test;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid date comparison operator: "' . $operator . '". Allowed operators: "==", "!=", "<", "<=", ">", ">="');
|
||||
},
|
||||
|
||||
/**
|
||||
|
@@ -60,7 +60,7 @@ class View
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return file_exists($this->file()) === true;
|
||||
return is_file($this->file()) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,13 +94,11 @@ class View
|
||||
throw new Exception($this->missingViewMessage());
|
||||
}
|
||||
|
||||
$exception = null;
|
||||
|
||||
ob_start();
|
||||
extract($this->data());
|
||||
|
||||
$exception = null;
|
||||
try {
|
||||
require $this->file();
|
||||
F::load($this->file(), null, $this->data());
|
||||
} catch (Throwable $e) {
|
||||
$exception = $e;
|
||||
}
|
||||
|
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace Kirby\Toolkit;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* XML parser and creator Class
|
||||
* XML parser and creator class
|
||||
*
|
||||
* @package Kirby Toolkit
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
@@ -14,7 +16,7 @@ namespace Kirby\Toolkit;
|
||||
class Xml
|
||||
{
|
||||
/**
|
||||
* Conversion table for html entities
|
||||
* HTML to XML conversion table for entities
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -54,166 +56,349 @@ class Xml
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates an XML string from an array
|
||||
* Closing string for void tags
|
||||
*
|
||||
* @param string $props The source array
|
||||
* @param string $name The name of the root element
|
||||
* @param bool $head Include the xml declaration head or not
|
||||
* @param int $level The indendation level
|
||||
* @return string The XML string
|
||||
* @var string
|
||||
*/
|
||||
public static function create($props, string $name = 'root', bool $head = true, $level = 0): 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
|
||||
{
|
||||
$attributes = $props['@attributes'] ?? null;
|
||||
$value = $props['@value'] ?? null;
|
||||
$children = $props;
|
||||
$indent = str_repeat(' ', $level);
|
||||
$nextLevel = $level + 1;
|
||||
if (is_array($name) === true) {
|
||||
if ($value !== false) {
|
||||
ksort($name);
|
||||
}
|
||||
|
||||
if (is_array($children) === true) {
|
||||
unset($children['@attributes'], $children['@value']);
|
||||
$attributes = [];
|
||||
foreach ($name as $key => $val) {
|
||||
$a = static::attr($key, $val);
|
||||
|
||||
$childTags = [];
|
||||
|
||||
foreach ($children as $childName => $childItems) {
|
||||
if (is_array($childItems) === true) {
|
||||
|
||||
// another tag with attributes
|
||||
if (A::isAssociative($childItems) === true) {
|
||||
$childTags[] = static::create($childItems, $childName, false, $level);
|
||||
|
||||
// just children
|
||||
} else {
|
||||
foreach ($childItems as $childItem) {
|
||||
$childTags[] = static::create($childItem, $childName, false, $nextLevel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$childTags[] = static::tag($childName, $childItems, null, $indent);
|
||||
if ($a) {
|
||||
$attributes[] = $a;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($childTags) === false) {
|
||||
$value = $childTags;
|
||||
}
|
||||
return implode(' ', $attributes);
|
||||
}
|
||||
|
||||
$result = $head === true ? '<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL : null;
|
||||
$result .= static::tag($name, $value, $attributes, $indent);
|
||||
if ($value === null || $value === '' || $value === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
if ($value === ' ') {
|
||||
return strtolower($name) . '=""';
|
||||
}
|
||||
|
||||
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, function ($value) {
|
||||
return !empty($value) || is_numeric($value);
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
$value = static::encode($value);
|
||||
}
|
||||
|
||||
return strtolower($name) . '="' . $value . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all xml entities from a string
|
||||
* and convert them to html entities first
|
||||
* and remove all html entities afterwards.
|
||||
* Creates an XML string from an array
|
||||
*
|
||||
* <code>
|
||||
* Supports special array keys `@name` (element name),
|
||||
* `@attributes` (XML attribute key-value array),
|
||||
* `@namespaces` (array with XML namespaces) and
|
||||
* `@value` (element content)
|
||||
*
|
||||
* echo xml::decode('some <em>über</em> crazy stuff');
|
||||
* // output: some über crazy stuff
|
||||
* @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 indendation 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
$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 = [];
|
||||
foreach ($props as $childItem) {
|
||||
$result[] = static::create($childItem, $name, false, $indent, $level);
|
||||
}
|
||||
|
||||
$result = implode(PHP_EOL, $result);
|
||||
}
|
||||
} else {
|
||||
// scalar value
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all HTML/XML tags and encoded chars from a string
|
||||
*
|
||||
* </code>
|
||||
* ```
|
||||
* echo Xml::decode('some über <em>crazy</em> stuff');
|
||||
* // output: some über crazy stuff
|
||||
* ```
|
||||
*
|
||||
* @param string $string
|
||||
* @param string|null $string
|
||||
* @return string
|
||||
*/
|
||||
public static function decode(string $string = null): string
|
||||
public static function decode(?string $string): string
|
||||
{
|
||||
return Html::decode($string);
|
||||
if ($string === null) {
|
||||
$string = '';
|
||||
}
|
||||
|
||||
$string = strip_tags($string);
|
||||
return html_entity_decode($string, ENT_COMPAT, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to a xml-safe string
|
||||
* Converts it to html-safe first and then it
|
||||
* will replace html entities to xml entities
|
||||
* Converts a string to an XML-safe string
|
||||
*
|
||||
* <code>
|
||||
* Converts it to HTML-safe first and then it
|
||||
* will replace HTML entities with XML entities
|
||||
*
|
||||
* echo xml::encode('some über crazy stuff');
|
||||
* ```php
|
||||
* echo Xml::encode('some über crazy stuff');
|
||||
* // output: some über crazy stuff
|
||||
* ```
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
* @param string $string
|
||||
* @param bool $html True: convert to html first
|
||||
* @param string|null $string
|
||||
* @param bool $html True = Convert to HTML-safe first
|
||||
* @return string
|
||||
*/
|
||||
public static function encode(string $string = null, bool $html = true): string
|
||||
public static function encode(?string $string, bool $html = true): string
|
||||
{
|
||||
if ($string === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($html === true) {
|
||||
$string = Html::encode($string, false);
|
||||
}
|
||||
|
||||
$entities = static::entities();
|
||||
$searches = array_keys($entities);
|
||||
$values = array_values($entities);
|
||||
$entities = self::entities();
|
||||
$html = array_keys($entities);
|
||||
$xml = array_values($entities);
|
||||
|
||||
return str_replace($searches, $values, $string);
|
||||
return str_replace($html, $xml, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the html to xml entities translation table
|
||||
* Returns the HTML-to-XML entity translation table
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function entities(): array
|
||||
{
|
||||
return static::$entities;
|
||||
return self::$entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a XML string and returns an array
|
||||
* Parses an XML string and returns an array
|
||||
*
|
||||
* @param string $xml
|
||||
* @return array|false
|
||||
* @return array|null Parsed array or `null` on error
|
||||
*/
|
||||
public static function parse(string $xml = null)
|
||||
public static function parse(string $xml): ?array
|
||||
{
|
||||
$xml = preg_replace('/(<\/?)(\w+):([^>]*>)/', '$1$2$3', $xml);
|
||||
$xml = @simplexml_load_string($xml, null, LIBXML_NOENT | LIBXML_NOCDATA);
|
||||
$xml = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
|
||||
|
||||
$xml = @json_encode($xml);
|
||||
$xml = @json_decode($xml, true);
|
||||
return is_array($xml) === true ? $xml : false;
|
||||
if (is_object($xml) !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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['']);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
|
||||
// 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;
|
||||
|
||||
if (count($array) > 0) {
|
||||
$array['@value'] = $element;
|
||||
|
||||
return $array;
|
||||
} else {
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an XML tag
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $content
|
||||
* @param array $attr
|
||||
* @param mixed $indent
|
||||
* @return string
|
||||
* @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 = null, array $attr = null, $indent = null): string
|
||||
public static function tag(string $name, $content = '', array $attr = null, ?string $indent = null, int $level = 0): string
|
||||
{
|
||||
$attr = Html::attr($attr);
|
||||
$start = '<' . $name . ($attr ? ' ' . $attr : null) . '>';
|
||||
$end = '</' . $name . '>';
|
||||
$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) {
|
||||
$xml = $indent . $start . PHP_EOL;
|
||||
foreach ($content as $line) {
|
||||
$xml .= $indent . $indent . $line . PHP_EOL;
|
||||
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;
|
||||
}
|
||||
$xml .= $indent . $end;
|
||||
} elseif ($content === null) {
|
||||
$xml = $baseIndent . $startShort;
|
||||
} else {
|
||||
$xml = $indent . $start . static::value($content) . $end;
|
||||
$xml = $baseIndent . $start . static::value($content) . $end;
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the value as cdata if necessary
|
||||
* Properly encodes tag contents
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
* @return string|null
|
||||
*/
|
||||
public static function value($value)
|
||||
public static function value($value): ?string
|
||||
{
|
||||
if ($value === true) {
|
||||
return 'true';
|
||||
@@ -224,23 +409,25 @@ class Xml
|
||||
}
|
||||
|
||||
if (is_numeric($value) === true) {
|
||||
return $value;
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Str::contains($value, '<![CDATA[') === true) {
|
||||
if (Str::startsWith($value, '<![CDATA[') === true) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$encoded = htmlentities($value);
|
||||
|
||||
if ($encoded === $value) {
|
||||
// no CDATA block needed
|
||||
return $value;
|
||||
}
|
||||
|
||||
return '<![CDATA[' . static::encode($value) . ']]>';
|
||||
// wrap everything in a CDATA block
|
||||
// and ensure that it is not closed in the input string
|
||||
return '<![CDATA[' . str_replace(']]>', ']]]]><![CDATA[>', $value) . ']]>';
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user