Upgrade to rc5

This commit is contained in:
Bastian Allgeier
2020-12-10 11:24:42 +01:00
parent 3fec0d7c93
commit c378376bc9
257 changed files with 13009 additions and 1846 deletions

View File

@@ -141,13 +141,13 @@ class A
*
* @param array $array1
* @param array $array2
* @param bool $mode Behavior for elements with numeric keys;
* A::MERGE_APPEND: elements are appended, keys are reset;
* A::MERGE_OVERWRITE: elements are overwritten, keys are preserved
* A::MERGE_REPLACE: non-associative arrays are completely replaced
* @param int $mode Behavior for elements with numeric keys;
* A::MERGE_APPEND: elements are appended, keys are reset;
* A::MERGE_OVERWRITE: elements are overwritten, keys are preserved
* A::MERGE_REPLACE: non-associative arrays are completely replaced
* @return array
*/
public static function merge($array1, $array2, $mode = A::MERGE_APPEND)
public static function merge($array1, $array2, int $mode = A::MERGE_APPEND)
{
$merged = $array1;
@@ -158,7 +158,7 @@ class A
foreach ($array2 as $key => $value) {
// append to the merged array, don't overwrite numeric keys
if (is_int($key) === true && $mode == static::MERGE_APPEND) {
if (is_int($key) === true && $mode === static::MERGE_APPEND) {
$merged[] = $value;
// recursively merge the two array values
@@ -171,7 +171,7 @@ class A
}
}
if ($mode == static::MERGE_APPEND) {
if ($mode === static::MERGE_APPEND) {
// the keys don't make sense anymore, reset them
// array_merge() is the simplest way to renumber
// arrays that have both numeric and string keys;
@@ -553,7 +553,7 @@ class A
*/
public static function sort(array $array, string $field, string $direction = 'desc', $method = SORT_REGULAR): array
{
$direction = strtolower($direction) == 'desc' ? SORT_DESC : SORT_ASC;
$direction = strtolower($direction) === 'desc' ? SORT_DESC : SORT_ASC;
$helper = [];
$result = [];

View File

@@ -236,55 +236,53 @@ class Collection extends Iterator implements Countable
return $collection->set($items);
}
/**
* Filters elements by a custom
* filter function or an array of filters
*
* @param array|\Closure $filter
* @return self
* @throws \Exception if $filter is neither a closure nor an array
*/
public function filter($filter)
{
if (is_callable($filter) === true) {
$collection = clone $this;
$collection->data = array_filter($this->data, $filter);
return $collection;
} elseif (is_array($filter) === true) {
$collection = $this;
foreach ($filter as $arguments) {
$collection = $collection->filterBy(...$arguments);
}
return $collection;
}
throw new Exception('The filter method needs either an array of filterBy rules or a closure function to be passed as parameter.');
}
/**
* Filters elements by one of the
* predefined filter methods.
* predefined filter methods, by a
* custom filter function or an array of filters
*
* @param string $field
* @param string|array|\Closure $field
* @param array ...$args
* @return \Kirby\Toolkit\Collection
*/
public function filterBy(string $field, ...$args)
public function filter($field, ...$args)
{
$operator = '==';
$test = $args[0] ?? null;
$split = $args[1] ?? false;
if (is_string($test) === true && isset(static::$filters[$test]) === true) {
// filter by custom filter function
if (is_callable($field) === true) {
$collection = clone $this;
$collection->data = array_filter($this->data, $field);
return $collection;
}
// array of filters
if (is_array($field) === true) {
$collection = $this;
foreach ($field as $filter) {
$collection = $collection->filter(...$filter);
}
return $collection;
}
if (
is_string($test) === true &&
isset(static::$filters[$test]) === true
) {
$operator = $test;
$test = $args[1] ?? null;
$split = $args[2] ?? false;
}
if (is_object($test) === true && method_exists($test, '__toString') === true) {
if (
is_object($test) === true &&
method_exists($test, '__toString') === true
) {
$test = (string)$test;
}
@@ -321,11 +319,18 @@ class Collection extends Iterator implements Countable
}
/**
* @param string $validator
* @param mixed $values
* @param mixed $test
* @return bool
* Alias for `Kirby\Toolkit\Collection::filter`
*
* @param string|Closure $field
* @param array ...$args
* @return self
*/
public function filterBy(...$args)
{
return $this->filter(...$args);
}
protected function filterMatchesAny($validator, $values, $test): bool
{
foreach ($values as $value) {
@@ -472,7 +477,7 @@ class Collection extends Iterator implements Countable
* Extracts an attribute value from the given element
* in the collection. This is useful if elements in the collection
* might be objects, arrays or anything else and you need to
* get the value independently from that. We use it for filterBy.
* get the value independently from that. We use it for `filter`.
*
* @param array|object $item
* @param string $attribute
@@ -516,69 +521,77 @@ class Collection extends Iterator implements Countable
}
/**
* Groups the elements by a given callback
* Groups the elements by a given field or callback function
*
* @param \Closure $callback
* @return \Kirby\Toolkit\Collection A new collection with an element for each group and a sub collection in each group
* @throws \Exception
* @param string|Closure $field
* @param bool $i
* @return \Kirby\Toolkit\Collection A new collection with an element for each group and a subcollection in each group
* @throws \Exception if $field is not a string nor a callback function
*/
public function group(Closure $callback)
public function group($field, bool $i = true)
{
$groups = [];
foreach ($this->data as $key => $item) {
// group by field name
if (is_string($field) === true) {
return $this->group(function ($item) use ($field, $i) {
$value = $this->getAttribute($item, $field);
// get the value to group by
$value = $callback($item);
// ignore upper/lowercase for group names
return $i === true ? Str::lower($value) : $value;
});
}
// make sure that there's always a proper value to group by
if (!$value) {
throw new Exception('Invalid grouping value for key: ' . $key);
}
// group via callback function
if (is_callable($field) === true) {
$groups = [];
// make sure we have a proper key for each group
if (is_array($value) === true) {
throw new Exception('You cannot group by arrays or objects');
} elseif (is_object($value) === true) {
if (method_exists($value, '__toString') === false) {
foreach ($this->data as $key => $item) {
// get the value to group by
$value = $field($item);
// make sure that there's always a proper value to group by
if (!$value) {
throw new Exception('Invalid grouping value for key: ' . $key);
}
// make sure we have a proper key for each group
if (is_array($value) === true) {
throw new Exception('You cannot group by arrays or objects');
} elseif (is_object($value) === true) {
if (method_exists($value, '__toString') === false) {
throw new Exception('You cannot group by arrays or objects');
} else {
$value = (string)$value;
}
}
if (isset($groups[$value]) === false) {
// create a new entry for the group if it does not exist yet
$groups[$value] = new static([$key => $item]);
} else {
$value = (string)$value;
// add the element to an existing group
$groups[$value]->set($key, $item);
}
}
if (isset($groups[$value]) === false) {
// create a new entry for the group if it does not exist yet
$groups[$value] = new static([$key => $item]);
} else {
// add the element to an existing group
$groups[$value]->set($key, $item);
}
return new Collection($groups);
}
return new Collection($groups);
throw new Exception('Can only group by string values or by providing a callback function');
}
/**
* Groups the elements by a given field
* Alias for `Kirby\Toolkit\Collection::group`
*
* @param string $field
* @param string|Closure $field
* @param bool $i
* @return \Kirby\Toolkit\Collection A new collection with an element for each group and a sub collection in each group
* @throws \Exception
*/
public function groupBy($field, bool $i = true)
public function groupBy(...$args)
{
if (is_string($field) === false) {
throw new Exception('Cannot group by non-string values. Did you mean to call group()?');
}
return $this->group(function ($item) use ($field, $i) {
$value = $this->getAttribute($item, $field);
// ignore upper/lowercase for group names
return $i === true ? Str::lower($value) : $value;
});
return $this->group(...$args);
}
/**
@@ -799,7 +812,7 @@ class Collection extends Iterator implements Countable
}
/**
* Runs a combination of filterBy, sortBy, not
* Runs a combination of filter, sort, not,
* offset, limit and paginate on the collection.
* Any part of the query is optional.
*
@@ -814,10 +827,17 @@ class Collection extends Iterator implements Countable
$result = $result->not(...$arguments['not']);
}
if (isset($arguments['filterBy']) === true) {
foreach ($arguments['filterBy'] as $filter) {
if (isset($filter['field']) === true && isset($filter['value']) === true) {
$result = $result->filterBy($filter['field'], $filter['operator'] ?? '==', $filter['value']);
if ($filters = $arguments['filterBy'] ?? $arguments['filter'] ?? null) {
foreach ($filters as $filter) {
if (
isset($filter['field']) === true &&
isset($filter['value']) === true
) {
$result = $result->filter(
$filter['field'],
$filter['operator'] ?? '==',
$filter['value']
);
}
}
}
@@ -830,18 +850,18 @@ class Collection extends Iterator implements Countable
$result = $result->limit($arguments['limit']);
}
if (isset($arguments['sortBy']) === true) {
if (is_array($arguments['sortBy'])) {
$sort = explode(' ', implode(' ', $arguments['sortBy']));
if ($sort = $arguments['sortBy'] ?? $arguments['sort'] ?? null) {
if (is_array($sort)) {
$sort = explode(' ', implode(' ', $sort));
} else {
// if there are commas in the sortBy argument, removes it
if (Str::contains($arguments['sortBy'], ',') === true) {
$arguments['sortBy'] = Str::replace($arguments['sortBy'], ',', '');
// if there are commas in the sort argument, removes it
if (Str::contains($sort, ',') === true) {
$sort = Str::replace($sort, ',', '');
}
$sort = explode(' ', $arguments['sortBy']);
$sort = explode(' ', $sort);
}
$result = $result->sortBy(...$sort);
$result = $result->sort(...$sort);
}
if (isset($arguments['paginate']) === true) {
@@ -924,17 +944,17 @@ class Collection extends Iterator implements Countable
/**
* Get sort arguments from a string
*
* @param string $sortBy
* @param string $sort
* @return array
*/
public static function sortArgs(string $sortBy): array
public static function sortArgs(string $sort): array
{
// if there are commas in the sortBy argument, removes it
if (Str::contains($sortBy, ',') === true) {
$sortBy = Str::replace($sortBy, ',', '');
if (Str::contains($sort, ',') === true) {
$sort = Str::replace($sort, ',', '');
}
$sortArgs = Str::split($sortBy, ' ');
$sortArgs = Str::split($sort, ' ');
// fill in PHP constants
array_walk($sortArgs, function (string &$value) {
@@ -954,7 +974,7 @@ class Collection extends Iterator implements Countable
* @param int $method The sort flag, SORT_REGULAR, SORT_NUMERIC etc.
* @return \Kirby\Toolkit\Collection
*/
public function sortBy()
public function sort()
{
// there is no need to sort empty collections
if (empty($this->data) === true) {
@@ -1062,6 +1082,19 @@ class Collection extends Iterator implements Countable
return $collection;
}
/**
* Alias for `Kirby\Toolkit\Collection::sort`
*
* @param string|callable $field Field name or value callback to sort by
* @param string $direction asc or desc
* @param int $method The sort flag, SORT_REGULAR, SORT_NUMERIC etc.
* @return Collection
*/
public function sortBy(...$args)
{
return $this->sort(...$args);
}
/**
* Converts the object into an array
*

View File

@@ -554,6 +554,10 @@ class F
*/
public static function read(string $file)
{
if (is_file($file) !== true) {
return false;
}
return @file_get_contents($file);
}
@@ -764,6 +768,18 @@ class F
return null;
}
/**
* Returns all extensions of a given file type
* or `null` if the file type is unknown
*
* @param string $type
* @return array|null
*/
public static function typeToExtensions(string $type): ?array
{
return static::$types[$type] ?? null;
}
/**
* Unzips a zip file
*

View File

@@ -3,6 +3,7 @@
namespace Kirby\Toolkit;
use Closure;
use NumberFormatter;
/**
* Localization class, roughly inspired by VueI18n
@@ -43,6 +44,13 @@ class I18n
*/
public static $fallback = 'en';
/**
* Cache of `NumberFormatter` objects by locale
*
* @var array
*/
protected static $decimalNumberFormatters = [];
/**
* Returns the fallback code
*
@@ -78,6 +86,25 @@ class I18n
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 = $locale ?? static::locale();
$formatter = static::decimalNumberFormatter($locale);
if ($formatter !== null) {
$number = $formatter->format($number);
}
return (string)$number;
}
/**
* Returns the locale code
*
@@ -189,6 +216,24 @@ class I18n
return static::$translations;
}
/**
* Returns (and creates) a decimal number formatter for a given locale
*
* @return \NumberFormatter|null
*/
protected static function decimalNumberFormatter(string $locale): ?NumberFormatter
{
if (isset(static::$decimalNumberFormatters[$locale])) {
return static::$decimalNumberFormatters[$locale];
}
if (extension_loaded('intl') !== true || class_exists('NumberFormatter') !== true) {
return null;
}
return static::$decimalNumberFormatters[$locale] = new NumberFormatter($locale, NumberFormatter::DECIMAL);
}
/**
* Translates amounts
*
@@ -202,10 +247,13 @@ class I18n
* @param string $key
* @param int $count
* @param string $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)
public static function translateCount(string $key, int $count, string $locale = null, bool $formatNumber = true)
{
$locale = $locale ?? static::locale();
$translation = static::translate($key, null, $locale);
if ($translation === null) {
@@ -226,6 +274,10 @@ class I18n
}
}
if ($formatNumber === true) {
$count = static::formatNumber($count, $locale);
}
return str_replace('{{ count }}', $count, $message);
}
}

101
kirby/src/Toolkit/Locale.php Executable file
View File

@@ -0,0 +1,101 @@
<?php
namespace Kirby\Toolkit;
use Kirby\Exception\InvalidArgumentException;
/**
* PHP locale handling
*
* @package Kirby Toolkit
* @author Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class Locale
{
/**
* 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
{
// list of all possible constant names
$constantNames = [
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY',
'LC_NUMERIC', 'LC_TIME', 'LC_MESSAGES'
];
// build an associative array with the locales
// that are actually supported on this system
$constants = [];
foreach ($constantNames as $name) {
if (defined($name) === true) {
$constants[constant($name)] = $name;
}
}
// 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;
}
/**
* 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) {
if (is_string($key) === true && Str::startsWith($key, 'LC_') === true) {
$key = constant($key);
}
$convertedLocale[$key] = $value;
}
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);
foreach ($locale as $key => $value) {
setlocale($key, $value);
}
}
}

View File

@@ -126,48 +126,30 @@ class Pagination
/**
* Getter for the current page
*
* @deprecated 3.3.0 Setter is no longer supported, use $pagination->clone()
* @return int
* @codeCoverageIgnore
*/
public function page(int $page = null): int
public function page(): int
{
if ($page !== null) {
throw new Exception('$pagination->page() setter is no longer supported, use $pagination->clone()'); // @codeCoverageIgnore
}
return $this->page;
}
/**
* Getter for the total number of items
*
* @deprecated 3.3.0 Setter is no longer supported, use $pagination->clone()
* @return int
* @codeCoverageIgnore
*/
public function total(int $total = null): int
public function total(): int
{
if ($total !== null) {
throw new Exception('$pagination->total() setter is no longer supported, use $pagination->clone()'); // @codeCoverageIgnore
}
return $this->total;
}
/**
* Getter for the number of items per page
*
* @deprecated 3.3.0 Setter is no longer supported, use $pagination->clone()
* @return int
* @codeCoverageIgnore
*/
public function limit(int $limit = null): int
public function limit(): int
{
if ($limit !== null) {
throw new Exception('$pagination->limit() setter is no longer supported, use $pagination->clone()'); // @codeCoverageIgnore
}
return $this->limit;
}

View File

@@ -289,7 +289,7 @@ class Str
for ($i = 0; $i < static::length($string); $i++) {
$char = static::substr($string, $i, 1);
list(, $code) = unpack('N', mb_convert_encoding($char, 'UCS-4BE', 'UTF-8'));
$encoded .= rand(1, 2) == 1 ? '&#' . $code . ';' : '&#x' . dechex($code) . ';';
$encoded .= rand(1, 2) === 1 ? '&#' . $code . ';' : '&#x' . dechex($code) . ';';
}
return $encoded;
@@ -957,7 +957,7 @@ class Str
* @param mixed $type
* @return mixed
*/
public static function toType($string = null, $type)
public static function toType($string, $type)
{
if (is_string($type) === false) {
$type = gettype($type);