file_cache_준비
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Iterators;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Smarter caching iterator.
|
||||
*
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read bool $empty
|
||||
* @property-read bool $odd
|
||||
* @property-read bool $even
|
||||
* @property-read int $counter
|
||||
* @property-read mixed $nextKey
|
||||
* @property-read mixed $nextValue
|
||||
*/
|
||||
class CachingIterator extends \CachingIterator implements \Countable
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
private $counter = 0;
|
||||
|
||||
|
||||
public function __construct($iterator)
|
||||
{
|
||||
if (is_array($iterator) || $iterator instanceof \stdClass) {
|
||||
$iterator = new \ArrayIterator($iterator);
|
||||
|
||||
} elseif ($iterator instanceof \IteratorAggregate) {
|
||||
do {
|
||||
$iterator = $iterator->getIterator();
|
||||
} while ($iterator instanceof \IteratorAggregate);
|
||||
assert($iterator instanceof \Iterator);
|
||||
|
||||
} elseif ($iterator instanceof \Iterator) {
|
||||
} elseif ($iterator instanceof \Traversable) {
|
||||
$iterator = new \IteratorIterator($iterator);
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', __CLASS__, is_object($iterator) ? get_class($iterator) : gettype($iterator)));
|
||||
}
|
||||
|
||||
parent::__construct($iterator, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current element the first one?
|
||||
*/
|
||||
public function isFirst(int $gridWidth = null): bool
|
||||
{
|
||||
return $this->counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current element the last one?
|
||||
*/
|
||||
public function isLast(int $gridWidth = null): bool
|
||||
{
|
||||
return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the iterator empty?
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->counter === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter odd?
|
||||
*/
|
||||
public function isOdd(): bool
|
||||
{
|
||||
return $this->counter % 2 === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter even?
|
||||
*/
|
||||
public function isEven(): bool
|
||||
{
|
||||
return $this->counter % 2 === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the counter.
|
||||
*/
|
||||
public function getCounter(): int
|
||||
{
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the count of elements.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$inner = $this->getInnerIterator();
|
||||
if ($inner instanceof \Countable) {
|
||||
return $inner->count();
|
||||
|
||||
} else {
|
||||
throw new Nette\NotSupportedException('Iterator is not countable.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forwards to the next element.
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
parent::next();
|
||||
if (parent::valid()) {
|
||||
$this->counter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rewinds the Iterator.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
parent::rewind();
|
||||
$this->counter = parent::valid() ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next key.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNextKey()
|
||||
{
|
||||
return $this->getInnerIterator()->key();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next element.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNextValue()
|
||||
{
|
||||
return $this->getInnerIterator()->current();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Iterators;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Applies the callback to the elements of the inner iterator.
|
||||
*/
|
||||
class Mapper extends \IteratorIterator
|
||||
{
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
|
||||
|
||||
public function __construct(\Traversable $iterator, callable $callback)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
|
||||
public function current()
|
||||
{
|
||||
return ($this->callback)(parent::current(), parent::key());
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Provides objects to work as array.
|
||||
*/
|
||||
class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
/** @return static */
|
||||
public static function from(array $arr, bool $recursive = true)
|
||||
{
|
||||
$obj = new static;
|
||||
foreach ($arr as $key => $value) {
|
||||
if ($recursive && is_array($value)) {
|
||||
$obj->$key = static::from($value, true);
|
||||
} else {
|
||||
$obj->$key = $value;
|
||||
}
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
*/
|
||||
public function getIterator(): \RecursiveArrayIterator
|
||||
{
|
||||
return new \RecursiveArrayIterator((array) $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count((array) $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces or appends a item.
|
||||
* @param string|int $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($key, $value): void
|
||||
{
|
||||
if (!is_scalar($key)) { // prevents null
|
||||
throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key)));
|
||||
}
|
||||
$this->$key = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a item.
|
||||
* @param string|int $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->$key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a item exists.
|
||||
* @param string|int $key
|
||||
*/
|
||||
public function offsetExists($key): bool
|
||||
{
|
||||
return isset($this->$key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the element from this list.
|
||||
* @param string|int $key
|
||||
*/
|
||||
public function offsetUnset($key): void
|
||||
{
|
||||
unset($this->$key);
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Provides the base class for a generic list (items can be accessed by index).
|
||||
*/
|
||||
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $list = [];
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
*/
|
||||
public function getIterator(): \ArrayIterator
|
||||
{
|
||||
return new \ArrayIterator($this->list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces or appends a item.
|
||||
* @param int|null $index
|
||||
* @param mixed $value
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
public function offsetSet($index, $value): void
|
||||
{
|
||||
if ($index === null) {
|
||||
$this->list[] = $value;
|
||||
|
||||
} elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) {
|
||||
throw new Nette\OutOfRangeException('Offset invalid or out of range');
|
||||
|
||||
} else {
|
||||
$this->list[$index] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a item.
|
||||
* @param int $index
|
||||
* @return mixed
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
public function offsetGet($index)
|
||||
{
|
||||
if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
|
||||
throw new Nette\OutOfRangeException('Offset invalid or out of range');
|
||||
}
|
||||
return $this->list[$index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a item exists.
|
||||
* @param int $index
|
||||
*/
|
||||
public function offsetExists($index): bool
|
||||
{
|
||||
return is_int($index) && $index >= 0 && $index < count($this->list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the element at the specified position in this list.
|
||||
* @param int $index
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
public function offsetUnset($index): void
|
||||
{
|
||||
if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
|
||||
throw new Nette\OutOfRangeException('Offset invalid or out of range');
|
||||
}
|
||||
array_splice($this->list, $index, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepends a item.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function prepend($value): void
|
||||
{
|
||||
$first = array_slice($this->list, 0, 1);
|
||||
$this->offsetSet(0, $value);
|
||||
array_splice($this->list, 1, 0, $first);
|
||||
}
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function is_array, is_int, is_object, count;
|
||||
|
||||
|
||||
/**
|
||||
* Array tools library.
|
||||
*/
|
||||
class Arrays
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Returns item from array or $default if item is not set.
|
||||
* @param string|int|array $key one or more keys
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function get(array $arr, $key, $default = null)
|
||||
{
|
||||
foreach (is_array($key) ? $key : [$key] as $k) {
|
||||
if (is_array($arr) && array_key_exists($k, $arr)) {
|
||||
$arr = $arr[$k];
|
||||
} else {
|
||||
if (func_num_args() < 3) {
|
||||
throw new Nette\InvalidArgumentException("Missing item '$k'.");
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns reference to array item.
|
||||
* @param string|int|array $key one or more keys
|
||||
* @return mixed
|
||||
* @throws Nette\InvalidArgumentException if traversed item is not an array
|
||||
*/
|
||||
public static function &getRef(array &$arr, $key)
|
||||
{
|
||||
foreach (is_array($key) ? $key : [$key] as $k) {
|
||||
if (is_array($arr) || $arr === null) {
|
||||
$arr = &$arr[$k];
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Traversed item is not an array.');
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively appends elements of remaining keys from the second array to the first.
|
||||
*/
|
||||
public static function mergeTree(array $arr1, array $arr2): array
|
||||
{
|
||||
$res = $arr1 + $arr2;
|
||||
foreach (array_intersect_key($arr1, $arr2) as $k => $v) {
|
||||
if (is_array($v) && is_array($arr2[$k])) {
|
||||
$res[$k] = self::mergeTree($v, $arr2[$k]);
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches the array for a given key and returns the offset if successful.
|
||||
* @param string|int $key
|
||||
* @return int|null offset if it is found, null otherwise
|
||||
*/
|
||||
public static function searchKey(array $arr, $key): ?int
|
||||
{
|
||||
$foo = [$key => null];
|
||||
return Helpers::falseToNull(array_search(key($foo), array_keys($arr), true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts new array before item specified by key.
|
||||
* @param string|int $key
|
||||
*/
|
||||
public static function insertBefore(array &$arr, $key, array $inserted): void
|
||||
{
|
||||
$offset = (int) self::searchKey($arr, $key);
|
||||
$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts new array after item specified by key.
|
||||
* @param string|int $key
|
||||
*/
|
||||
public static function insertAfter(array &$arr, $key, array $inserted): void
|
||||
{
|
||||
$offset = self::searchKey($arr, $key);
|
||||
$offset = $offset === null ? count($arr) : $offset + 1;
|
||||
$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renames key in array.
|
||||
* @param string|int $oldKey
|
||||
* @param string|int $newKey
|
||||
*/
|
||||
public static function renameKey(array &$arr, $oldKey, $newKey): void
|
||||
{
|
||||
$offset = self::searchKey($arr, $oldKey);
|
||||
if ($offset !== null) {
|
||||
$keys = array_keys($arr);
|
||||
$keys[$offset] = $newKey;
|
||||
$arr = array_combine($keys, $arr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns array entries that match the pattern.
|
||||
*/
|
||||
public static function grep(array $arr, string $pattern, int $flags = 0): array
|
||||
{
|
||||
return Strings::pcre('preg_grep', [$pattern, $arr, $flags]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns flattened array.
|
||||
*/
|
||||
public static function flatten(array $arr, bool $preserveKeys = false): array
|
||||
{
|
||||
$res = [];
|
||||
$cb = $preserveKeys
|
||||
? function ($v, $k) use (&$res): void { $res[$k] = $v; }
|
||||
: function ($v) use (&$res): void { $res[] = $v; };
|
||||
array_walk_recursive($arr, $cb);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a variable is a zero-based integer indexed array.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isList($value): bool
|
||||
{
|
||||
return is_array($value) && (!$value || array_keys($value) === range(0, count($value) - 1));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
|
||||
* @param string|string[] $path
|
||||
* @return array|\stdClass
|
||||
*/
|
||||
public static function associate(array $arr, $path)
|
||||
{
|
||||
$parts = is_array($path)
|
||||
? $path
|
||||
: preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
|
||||
throw new Nette\InvalidArgumentException("Invalid path '$path'.");
|
||||
}
|
||||
|
||||
$res = $parts[0] === '->' ? new \stdClass : [];
|
||||
|
||||
foreach ($arr as $rowOrig) {
|
||||
$row = (array) $rowOrig;
|
||||
$x = &$res;
|
||||
|
||||
for ($i = 0; $i < count($parts); $i++) {
|
||||
$part = $parts[$i];
|
||||
if ($part === '[]') {
|
||||
$x = &$x[];
|
||||
|
||||
} elseif ($part === '=') {
|
||||
if (isset($parts[++$i])) {
|
||||
$x = $row[$parts[$i]];
|
||||
$row = null;
|
||||
}
|
||||
|
||||
} elseif ($part === '->') {
|
||||
if (isset($parts[++$i])) {
|
||||
if ($x === null) {
|
||||
$x = new \stdClass;
|
||||
}
|
||||
$x = &$x->{$row[$parts[$i]]};
|
||||
} else {
|
||||
$row = is_object($rowOrig) ? $rowOrig : (object) $row;
|
||||
}
|
||||
|
||||
} elseif ($part !== '|') {
|
||||
$x = &$x[(string) $row[$part]];
|
||||
}
|
||||
}
|
||||
|
||||
if ($x === null) {
|
||||
$x = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes to associative array.
|
||||
* @param mixed $filling
|
||||
*/
|
||||
public static function normalize(array $arr, $filling = null): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($arr as $k => $v) {
|
||||
$res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Picks element from the array by key and return its value.
|
||||
* @param string|int $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function pick(array &$arr, $key, $default = null)
|
||||
{
|
||||
if (array_key_exists($key, $arr)) {
|
||||
$value = $arr[$key];
|
||||
unset($arr[$key]);
|
||||
return $value;
|
||||
|
||||
} elseif (func_num_args() < 3) {
|
||||
throw new Nette\InvalidArgumentException("Missing item '$key'.");
|
||||
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether some element in the array passes the callback test.
|
||||
*/
|
||||
public static function some(array $arr, callable $callback): bool
|
||||
{
|
||||
foreach ($arr as $k => $v) {
|
||||
if ($callback($v, $k, $arr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether all elements in the array pass the callback test.
|
||||
*/
|
||||
public static function every(array $arr, callable $callback): bool
|
||||
{
|
||||
foreach ($arr as $k => $v) {
|
||||
if (!$callback($v, $k, $arr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies the callback to the elements of the array.
|
||||
*/
|
||||
public static function map(array $arr, callable $callback): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($arr as $k => $v) {
|
||||
$res[$k] = $callback($v, $k, $arr);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts array to object
|
||||
* @param object $obj
|
||||
* @return object
|
||||
*/
|
||||
public static function toObject(array $arr, $obj)
|
||||
{
|
||||
foreach ($arr as $k => $v) {
|
||||
$obj->$k = $v;
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function is_array, is_object, is_string;
|
||||
|
||||
|
||||
/**
|
||||
* PHP callable tools.
|
||||
*/
|
||||
final class Callback
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* @param string|object|callable $callable class, object, callable
|
||||
* @deprecated use Closure::fromCallable()
|
||||
*/
|
||||
public static function closure($callable, string $method = null): \Closure
|
||||
{
|
||||
try {
|
||||
return \Closure::fromCallable($method === null ? $callable : [$callable, $method]);
|
||||
} catch (\TypeError $e) {
|
||||
throw new Nette\InvalidArgumentException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes callback.
|
||||
* @return mixed
|
||||
* @deprecated
|
||||
*/
|
||||
public static function invoke($callable, ...$args)
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
|
||||
self::check($callable);
|
||||
return $callable(...$args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes callback with an array of parameters.
|
||||
* @return mixed
|
||||
* @deprecated
|
||||
*/
|
||||
public static function invokeArgs($callable, array $args = [])
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
|
||||
self::check($callable);
|
||||
return $callable(...$args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes internal PHP function with own error handler.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function invokeSafe(string $function, array $args, callable $onError)
|
||||
{
|
||||
$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
|
||||
if ($file === __FILE__) {
|
||||
$msg = ini_get('html_errors') ? Html::htmlToText($message) : $message;
|
||||
$msg = preg_replace("#^$function\(.*?\): #", '', $msg);
|
||||
if ($onError($msg, $severity) !== false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $prev ? $prev(...func_get_args()) : false;
|
||||
});
|
||||
|
||||
try {
|
||||
return $function(...$args);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $callable
|
||||
* @return callable
|
||||
*/
|
||||
public static function check($callable, bool $syntax = false)
|
||||
{
|
||||
if (!is_callable($callable, $syntax)) {
|
||||
throw new Nette\InvalidArgumentException($syntax
|
||||
? 'Given value is not a callable type.'
|
||||
: sprintf("Callback '%s' is not callable.", self::toString($callable))
|
||||
);
|
||||
}
|
||||
return $callable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $callable may be syntactically correct but not callable
|
||||
*/
|
||||
public static function toString($callable): string
|
||||
{
|
||||
if ($callable instanceof \Closure) {
|
||||
$inner = self::unwrap($callable);
|
||||
return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
|
||||
} elseif (is_string($callable) && $callable[0] === "\0") {
|
||||
return '{lambda}';
|
||||
} else {
|
||||
is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual);
|
||||
return $textual;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param callable $callable is escalated to ReflectionException
|
||||
* @return \ReflectionMethod|\ReflectionFunction
|
||||
*/
|
||||
public static function toReflection($callable): \ReflectionFunctionAbstract
|
||||
{
|
||||
if ($callable instanceof \Closure) {
|
||||
$callable = self::unwrap($callable);
|
||||
}
|
||||
|
||||
if (is_string($callable) && strpos($callable, '::')) {
|
||||
return new \ReflectionMethod($callable);
|
||||
} elseif (is_array($callable)) {
|
||||
return new \ReflectionMethod($callable[0], $callable[1]);
|
||||
} elseif (is_object($callable) && !$callable instanceof \Closure) {
|
||||
return new \ReflectionMethod($callable, '__invoke');
|
||||
} else {
|
||||
return new \ReflectionFunction($callable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function isStatic(callable $callable): bool
|
||||
{
|
||||
return is_array($callable) ? is_string($callable[0]) : is_string($callable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unwraps closure created by Closure::fromCallable()
|
||||
* @internal
|
||||
*/
|
||||
public static function unwrap(\Closure $closure): callable
|
||||
{
|
||||
$r = new \ReflectionFunction($closure);
|
||||
if (substr($r->name, -1) === '}') {
|
||||
return $closure;
|
||||
|
||||
} elseif ($obj = $r->getClosureThis()) {
|
||||
return [$obj, $r->name];
|
||||
|
||||
} elseif ($class = $r->getClosureScopeClass()) {
|
||||
return [$class->name, $r->name];
|
||||
|
||||
} else {
|
||||
return $r->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* DateTime.
|
||||
*/
|
||||
class DateTime extends \DateTime implements \JsonSerializable
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** minute in seconds */
|
||||
public const MINUTE = 60;
|
||||
|
||||
/** hour in seconds */
|
||||
public const HOUR = 60 * self::MINUTE;
|
||||
|
||||
/** day in seconds */
|
||||
public const DAY = 24 * self::HOUR;
|
||||
|
||||
/** week in seconds */
|
||||
public const WEEK = 7 * self::DAY;
|
||||
|
||||
/** average month in seconds */
|
||||
public const MONTH = 2629800;
|
||||
|
||||
/** average year in seconds */
|
||||
public const YEAR = 31557600;
|
||||
|
||||
|
||||
/**
|
||||
* DateTime object factory.
|
||||
* @param string|int|\DateTimeInterface $time
|
||||
* @return static
|
||||
*/
|
||||
public static function from($time)
|
||||
{
|
||||
if ($time instanceof \DateTimeInterface) {
|
||||
return new static($time->format('Y-m-d H:i:s.u'), $time->getTimezone());
|
||||
|
||||
} elseif (is_numeric($time)) {
|
||||
if ($time <= self::YEAR) {
|
||||
$time += time();
|
||||
}
|
||||
return (new static('@' . $time))->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
|
||||
} else { // textual or null
|
||||
return new static((string) $time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates DateTime object.
|
||||
* @return static
|
||||
*/
|
||||
public static function fromParts(int $year, int $month, int $day, int $hour = 0, int $minute = 0, float $second = 0.0)
|
||||
{
|
||||
$s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5f', $year, $month, $day, $hour, $minute, $second);
|
||||
if (!checkdate($month, $day, $year) || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 || $second < 0 || $second >= 60) {
|
||||
throw new Nette\InvalidArgumentException("Invalid date '$s'");
|
||||
}
|
||||
return new static($s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns new DateTime object formatted according to the specified format.
|
||||
* @param string $format The format the $time parameter should be in
|
||||
* @param string $time
|
||||
* @param string|\DateTimeZone $timezone (default timezone is used if null is passed)
|
||||
* @return static|false
|
||||
*/
|
||||
public static function createFromFormat($format, $time, $timezone = null)
|
||||
{
|
||||
if ($timezone === null) {
|
||||
$timezone = new \DateTimeZone(date_default_timezone_get());
|
||||
|
||||
} elseif (is_string($timezone)) {
|
||||
$timezone = new \DateTimeZone($timezone);
|
||||
|
||||
} elseif (!$timezone instanceof \DateTimeZone) {
|
||||
throw new Nette\InvalidArgumentException('Invalid timezone given');
|
||||
}
|
||||
|
||||
$date = parent::createFromFormat($format, $time, $timezone);
|
||||
return $date ? static::from($date) : false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns JSON representation in ISO 8601 (used by JavaScript).
|
||||
*/
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->format('c');
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function modifyClone(string $modify = '')
|
||||
{
|
||||
$dolly = clone $this;
|
||||
return $modify ? $dolly->modify($modify) : $dolly;
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* File system tool.
|
||||
*/
|
||||
final class FileSystem
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Creates a directory.
|
||||
* @throws Nette\IOException
|
||||
*/
|
||||
public static function createDir(string $dir, int $mode = 0777): void
|
||||
{
|
||||
if (!is_dir($dir) && !@mkdir($dir, $mode, true) && !is_dir($dir)) { // @ - dir may already exist
|
||||
throw new Nette\IOException("Unable to create directory '$dir'. " . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies a file or directory.
|
||||
* @throws Nette\IOException
|
||||
*/
|
||||
public static function copy(string $source, string $dest, bool $overwrite = true): void
|
||||
{
|
||||
if (stream_is_local($source) && !file_exists($source)) {
|
||||
throw new Nette\IOException("File or directory '$source' not found.");
|
||||
|
||||
} elseif (!$overwrite && file_exists($dest)) {
|
||||
throw new Nette\InvalidStateException("File or directory '$dest' already exists.");
|
||||
|
||||
} elseif (is_dir($source)) {
|
||||
static::createDir($dest);
|
||||
foreach (new \FilesystemIterator($dest) as $item) {
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
|
||||
if ($item->isDir()) {
|
||||
static::createDir($dest . '/' . $iterator->getSubPathName());
|
||||
} else {
|
||||
static::copy($item->getPathname(), $dest . '/' . $iterator->getSubPathName());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
static::createDir(dirname($dest));
|
||||
if (($s = @fopen($source, 'rb')) && ($d = @fopen($dest, 'wb')) && @stream_copy_to_stream($s, $d) === false) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to copy file '$source' to '$dest'. " . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a file or directory.
|
||||
* @throws Nette\IOException
|
||||
*/
|
||||
public static function delete(string $path): void
|
||||
{
|
||||
if (is_file($path) || is_link($path)) {
|
||||
$func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink';
|
||||
if (!@$func($path)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to delete '$path'. " . Helpers::getLastError());
|
||||
}
|
||||
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
if (!@rmdir($path)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to delete directory '$path'. " . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renames a file or directory.
|
||||
* @throws Nette\IOException
|
||||
* @throws Nette\InvalidStateException if the target file or directory already exist
|
||||
*/
|
||||
public static function rename(string $name, string $newName, bool $overwrite = true): void
|
||||
{
|
||||
if (!$overwrite && file_exists($newName)) {
|
||||
throw new Nette\InvalidStateException("File or directory '$newName' already exists.");
|
||||
|
||||
} elseif (!file_exists($name)) {
|
||||
throw new Nette\IOException("File or directory '$name' not found.");
|
||||
|
||||
} else {
|
||||
static::createDir(dirname($newName));
|
||||
if (realpath($name) !== realpath($newName)) {
|
||||
static::delete($newName);
|
||||
}
|
||||
if (!@rename($name, $newName)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to rename file or directory '$name' to '$newName'. " . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads file content.
|
||||
* @throws Nette\IOException
|
||||
*/
|
||||
public static function read(string $file): string
|
||||
{
|
||||
$content = @file_get_contents($file); // @ is escalated to exception
|
||||
if ($content === false) {
|
||||
throw new Nette\IOException("Unable to read file '$file'. " . Helpers::getLastError());
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes a string to a file.
|
||||
* @throws Nette\IOException
|
||||
*/
|
||||
public static function write(string $file, string $content, ?int $mode = 0666): void
|
||||
{
|
||||
static::createDir(dirname($file));
|
||||
if (@file_put_contents($file, $content) === false) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to write file '$file'. " . Helpers::getLastError());
|
||||
}
|
||||
if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to chmod file '$file' to mode " . decoct($mode) . '. ' . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is path absolute?
|
||||
*/
|
||||
public static function isAbsolute(string $path): bool
|
||||
{
|
||||
return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes ../. and directory separators in path.
|
||||
*/
|
||||
public static function normalizePath(string $path): string
|
||||
{
|
||||
$parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path);
|
||||
$res = [];
|
||||
foreach ($parts as $part) {
|
||||
if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
|
||||
array_pop($res);
|
||||
} elseif ($part !== '.') {
|
||||
$res[] = $part;
|
||||
}
|
||||
}
|
||||
return $res === ['']
|
||||
? DIRECTORY_SEPARATOR
|
||||
: implode(DIRECTORY_SEPARATOR, $res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Joins all given path segments then normalizes the resulting path.
|
||||
*/
|
||||
public static function joinPaths(string ...$paths): string
|
||||
{
|
||||
return self::normalizePath(implode('/', $paths));
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
|
||||
class Helpers
|
||||
{
|
||||
/**
|
||||
* Captures PHP output into a string.
|
||||
*/
|
||||
public static function capture(callable $func): string
|
||||
{
|
||||
ob_start(function () {});
|
||||
try {
|
||||
$func();
|
||||
return ob_get_clean();
|
||||
} catch (\Throwable $e) {
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last PHP error as plain string.
|
||||
*/
|
||||
public static function getLastError(): string
|
||||
{
|
||||
$message = error_get_last()['message'] ?? '';
|
||||
$message = ini_get('html_errors') ? Html::htmlToText($message) : $message;
|
||||
$message = preg_replace('#^\w+\(.*?\): #', '', $message);
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts false to null.
|
||||
* @param mixed $val
|
||||
* @return mixed
|
||||
*/
|
||||
public static function falseToNull($val)
|
||||
{
|
||||
return $val === false ? null : $val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the best suggestion (for 8-bit encoding).
|
||||
* @param string[] $possibilities
|
||||
*/
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
{
|
||||
$best = null;
|
||||
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
||||
foreach (array_unique($possibilities) as $item) {
|
||||
if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) {
|
||||
$min = $len;
|
||||
$best = $item;
|
||||
}
|
||||
}
|
||||
return $best;
|
||||
}
|
||||
}
|
||||
+870
@@ -0,0 +1,870 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function is_array, is_float, is_object, is_string;
|
||||
|
||||
|
||||
/**
|
||||
* HTML helper.
|
||||
*
|
||||
* @property string|null $accept
|
||||
* @property string|null $accesskey
|
||||
* @property string|null $action
|
||||
* @property string|null $align
|
||||
* @property string|null $allow
|
||||
* @property string|null $alt
|
||||
* @property bool|null $async
|
||||
* @property string|null $autocapitalize
|
||||
* @property string|null $autocomplete
|
||||
* @property bool|null $autofocus
|
||||
* @property bool|null $autoplay
|
||||
* @property string|null $charset
|
||||
* @property bool|null $checked
|
||||
* @property string|null $cite
|
||||
* @property string|null $class
|
||||
* @property int|null $cols
|
||||
* @property int|null $colspan
|
||||
* @property string|null $content
|
||||
* @property bool|null $contenteditable
|
||||
* @property bool|null $controls
|
||||
* @property string|null $coords
|
||||
* @property string|null $crossorigin
|
||||
* @property string|null $data
|
||||
* @property string|null $datetime
|
||||
* @property string|null $decoding
|
||||
* @property bool|null $default
|
||||
* @property bool|null $defer
|
||||
* @property string|null $dir
|
||||
* @property string|null $dirname
|
||||
* @property bool|null $disabled
|
||||
* @property bool|null $download
|
||||
* @property string|null $draggable
|
||||
* @property string|null $dropzone
|
||||
* @property string|null $enctype
|
||||
* @property string|null $for
|
||||
* @property string|null $form
|
||||
* @property string|null $formaction
|
||||
* @property string|null $formenctype
|
||||
* @property string|null $formmethod
|
||||
* @property bool|null $formnovalidate
|
||||
* @property string|null $formtarget
|
||||
* @property string|null $headers
|
||||
* @property int|null $height
|
||||
* @property bool|null $hidden
|
||||
* @property float|null $high
|
||||
* @property string|null $href
|
||||
* @property string|null $hreflang
|
||||
* @property string|null $id
|
||||
* @property string|null $integrity
|
||||
* @property string|null $inputmode
|
||||
* @property bool|null $ismap
|
||||
* @property string|null $itemprop
|
||||
* @property string|null $kind
|
||||
* @property string|null $label
|
||||
* @property string|null $lang
|
||||
* @property string|null $list
|
||||
* @property bool|null $loop
|
||||
* @property float|null $low
|
||||
* @property float|null $max
|
||||
* @property int|null $maxlength
|
||||
* @property int|null $minlength
|
||||
* @property string|null $media
|
||||
* @property string|null $method
|
||||
* @property float|null $min
|
||||
* @property bool|null $multiple
|
||||
* @property bool|null $muted
|
||||
* @property string|null $name
|
||||
* @property bool|null $novalidate
|
||||
* @property bool|null $open
|
||||
* @property float|null $optimum
|
||||
* @property string|null $pattern
|
||||
* @property string|null $ping
|
||||
* @property string|null $placeholder
|
||||
* @property string|null $poster
|
||||
* @property string|null $preload
|
||||
* @property string|null $radiogroup
|
||||
* @property bool|null $readonly
|
||||
* @property string|null $rel
|
||||
* @property bool|null $required
|
||||
* @property bool|null $reversed
|
||||
* @property int|null $rows
|
||||
* @property int|null $rowspan
|
||||
* @property string|null $sandbox
|
||||
* @property string|null $scope
|
||||
* @property bool|null $selected
|
||||
* @property string|null $shape
|
||||
* @property int|null $size
|
||||
* @property string|null $sizes
|
||||
* @property string|null $slot
|
||||
* @property int|null $span
|
||||
* @property string|null $spellcheck
|
||||
* @property string|null $src
|
||||
* @property string|null $srcdoc
|
||||
* @property string|null $srclang
|
||||
* @property string|null $srcset
|
||||
* @property int|null $start
|
||||
* @property float|null $step
|
||||
* @property string|null $style
|
||||
* @property int|null $tabindex
|
||||
* @property string|null $target
|
||||
* @property string|null $title
|
||||
* @property string|null $translate
|
||||
* @property string|null $type
|
||||
* @property string|null $usemap
|
||||
* @property string|null $value
|
||||
* @property int|null $width
|
||||
* @property string|null $wrap
|
||||
*
|
||||
* @method self accept(?string $val)
|
||||
* @method self accesskey(?string $val, bool $state = null)
|
||||
* @method self action(?string $val)
|
||||
* @method self align(?string $val)
|
||||
* @method self allow(?string $val, bool $state = null)
|
||||
* @method self alt(?string $val)
|
||||
* @method self async(?bool $val)
|
||||
* @method self autocapitalize(?string $val)
|
||||
* @method self autocomplete(?string $val)
|
||||
* @method self autofocus(?bool $val)
|
||||
* @method self autoplay(?bool $val)
|
||||
* @method self charset(?string $val)
|
||||
* @method self checked(?bool $val)
|
||||
* @method self cite(?string $val)
|
||||
* @method self class(?string $val, bool $state = null)
|
||||
* @method self cols(?int $val)
|
||||
* @method self colspan(?int $val)
|
||||
* @method self content(?string $val)
|
||||
* @method self contenteditable(?bool $val)
|
||||
* @method self controls(?bool $val)
|
||||
* @method self coords(?string $val)
|
||||
* @method self crossorigin(?string $val)
|
||||
* @method self datetime(?string $val)
|
||||
* @method self decoding(?string $val)
|
||||
* @method self default(?bool $val)
|
||||
* @method self defer(?bool $val)
|
||||
* @method self dir(?string $val)
|
||||
* @method self dirname(?string $val)
|
||||
* @method self disabled(?bool $val)
|
||||
* @method self download(?bool $val)
|
||||
* @method self draggable(?string $val)
|
||||
* @method self dropzone(?string $val)
|
||||
* @method self enctype(?string $val)
|
||||
* @method self for(?string $val)
|
||||
* @method self form(?string $val)
|
||||
* @method self formaction(?string $val)
|
||||
* @method self formenctype(?string $val)
|
||||
* @method self formmethod(?string $val)
|
||||
* @method self formnovalidate(?bool $val)
|
||||
* @method self formtarget(?string $val)
|
||||
* @method self headers(?string $val, bool $state = null)
|
||||
* @method self height(?int $val)
|
||||
* @method self hidden(?bool $val)
|
||||
* @method self high(?float $val)
|
||||
* @method self hreflang(?string $val)
|
||||
* @method self id(?string $val)
|
||||
* @method self integrity(?string $val)
|
||||
* @method self inputmode(?string $val)
|
||||
* @method self ismap(?bool $val)
|
||||
* @method self itemprop(?string $val)
|
||||
* @method self kind(?string $val)
|
||||
* @method self label(?string $val)
|
||||
* @method self lang(?string $val)
|
||||
* @method self list(?string $val)
|
||||
* @method self loop(?bool $val)
|
||||
* @method self low(?float $val)
|
||||
* @method self max(?float $val)
|
||||
* @method self maxlength(?int $val)
|
||||
* @method self minlength(?int $val)
|
||||
* @method self media(?string $val)
|
||||
* @method self method(?string $val)
|
||||
* @method self min(?float $val)
|
||||
* @method self multiple(?bool $val)
|
||||
* @method self muted(?bool $val)
|
||||
* @method self name(?string $val)
|
||||
* @method self novalidate(?bool $val)
|
||||
* @method self open(?bool $val)
|
||||
* @method self optimum(?float $val)
|
||||
* @method self pattern(?string $val)
|
||||
* @method self ping(?string $val, bool $state = null)
|
||||
* @method self placeholder(?string $val)
|
||||
* @method self poster(?string $val)
|
||||
* @method self preload(?string $val)
|
||||
* @method self radiogroup(?string $val)
|
||||
* @method self readonly(?bool $val)
|
||||
* @method self rel(?string $val)
|
||||
* @method self required(?bool $val)
|
||||
* @method self reversed(?bool $val)
|
||||
* @method self rows(?int $val)
|
||||
* @method self rowspan(?int $val)
|
||||
* @method self sandbox(?string $val, bool $state = null)
|
||||
* @method self scope(?string $val)
|
||||
* @method self selected(?bool $val)
|
||||
* @method self shape(?string $val)
|
||||
* @method self size(?int $val)
|
||||
* @method self sizes(?string $val)
|
||||
* @method self slot(?string $val)
|
||||
* @method self span(?int $val)
|
||||
* @method self spellcheck(?string $val)
|
||||
* @method self src(?string $val)
|
||||
* @method self srcdoc(?string $val)
|
||||
* @method self srclang(?string $val)
|
||||
* @method self srcset(?string $val)
|
||||
* @method self start(?int $val)
|
||||
* @method self step(?float $val)
|
||||
* @method self style(?string $property, string $val = null)
|
||||
* @method self tabindex(?int $val)
|
||||
* @method self target(?string $val)
|
||||
* @method self title(?string $val)
|
||||
* @method self translate(?string $val)
|
||||
* @method self type(?string $val)
|
||||
* @method self usemap(?string $val)
|
||||
* @method self value(?string $val)
|
||||
* @method self width(?int $val)
|
||||
* @method self wrap(?string $val)
|
||||
*/
|
||||
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array<string, mixed> element's attributes */
|
||||
public $attrs = [];
|
||||
|
||||
/** @var bool use XHTML syntax? */
|
||||
public static $xhtml = false;
|
||||
|
||||
/** @var array<string, int> void elements */
|
||||
public static $emptyElements = [
|
||||
'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
|
||||
'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
|
||||
'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
|
||||
];
|
||||
|
||||
/** @var array<int, Html|string> nodes */
|
||||
protected $children = [];
|
||||
|
||||
/** @var string element's name */
|
||||
private $name;
|
||||
|
||||
/** @var bool is element empty? */
|
||||
private $isEmpty;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs new HTML element.
|
||||
* @param array|string $attrs element's attributes or plain text content
|
||||
* @return static
|
||||
*/
|
||||
public static function el(string $name = null, $attrs = null)
|
||||
{
|
||||
$el = new static;
|
||||
$parts = explode(' ', (string) $name, 2);
|
||||
$el->setName($parts[0]);
|
||||
|
||||
if (is_array($attrs)) {
|
||||
$el->attrs = $attrs;
|
||||
|
||||
} elseif ($attrs !== null) {
|
||||
$el->setText($attrs);
|
||||
}
|
||||
|
||||
if (isset($parts[1])) {
|
||||
foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) {
|
||||
$el->attrs[$m[1]] = $m[3] ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
return $el;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object representing HTML text.
|
||||
*/
|
||||
public static function fromHtml(string $html): self
|
||||
{
|
||||
return (new static)->setHtml($html);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object representing plain text.
|
||||
*/
|
||||
public static function fromText(string $text): self
|
||||
{
|
||||
return (new static)->setText($text);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts to HTML.
|
||||
*/
|
||||
final public function toHtml(): string
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts to plain text.
|
||||
*/
|
||||
final public function toText(): string
|
||||
{
|
||||
return $this->getText();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts given HTML code to plain text.
|
||||
*/
|
||||
public static function htmlToText(string $html): string
|
||||
{
|
||||
return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes element's name.
|
||||
* @return static
|
||||
*/
|
||||
final public function setName(string $name, bool $isEmpty = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's name.
|
||||
*/
|
||||
final public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is element empty?
|
||||
*/
|
||||
final public function isEmpty(): bool
|
||||
{
|
||||
return $this->isEmpty;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets multiple attributes.
|
||||
* @return static
|
||||
*/
|
||||
public function addAttributes(array $attrs)
|
||||
{
|
||||
$this->attrs = array_merge($this->attrs, $attrs);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends value to element's attribute.
|
||||
* @param mixed $value
|
||||
* @param mixed $option
|
||||
* @return static
|
||||
*/
|
||||
public function appendAttribute(string $name, $value, $option = true)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
|
||||
$this->attrs[$name] = $value + $prev;
|
||||
|
||||
} elseif ((string) $value === '') {
|
||||
$tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
|
||||
|
||||
} elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
|
||||
$this->attrs[$name][$value] = $option;
|
||||
|
||||
} else {
|
||||
$this->attrs[$name] = [$this->attrs[$name] => true, $value => $option];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets element's attribute.
|
||||
* @param mixed $value
|
||||
* @return static
|
||||
*/
|
||||
public function setAttribute(string $name, $value)
|
||||
{
|
||||
$this->attrs[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's attribute.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute(string $name)
|
||||
{
|
||||
return $this->attrs[$name] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unsets element's attribute.
|
||||
* @return static
|
||||
*/
|
||||
public function removeAttribute(string $name)
|
||||
{
|
||||
unset($this->attrs[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unsets element's attributes.
|
||||
* @return static
|
||||
*/
|
||||
public function removeAttributes(array $attributes)
|
||||
{
|
||||
foreach ($attributes as $name) {
|
||||
unset($this->attrs[$name]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* @param mixed $value
|
||||
*/
|
||||
final public function __set(string $name, $value): void
|
||||
{
|
||||
$this->attrs[$name] = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded getter for element's attribute.
|
||||
* @return mixed
|
||||
*/
|
||||
final public function &__get(string $name)
|
||||
{
|
||||
return $this->attrs[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded tester for element's attribute.
|
||||
*/
|
||||
final public function __isset(string $name): bool
|
||||
{
|
||||
return isset($this->attrs[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded unsetter for element's attribute.
|
||||
*/
|
||||
final public function __unset(string $name): void
|
||||
{
|
||||
unset($this->attrs[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* @return mixed
|
||||
*/
|
||||
final public function __call(string $m, array $args)
|
||||
{
|
||||
$p = substr($m, 0, 3);
|
||||
if ($p === 'get' || $p === 'set' || $p === 'add') {
|
||||
$m = substr($m, 3);
|
||||
$m[0] = $m[0] | "\x20";
|
||||
if ($p === 'get') {
|
||||
return $this->attrs[$m] ?? null;
|
||||
|
||||
} elseif ($p === 'add') {
|
||||
$args[] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($args) === 0) { // invalid
|
||||
|
||||
} elseif (count($args) === 1) { // set
|
||||
$this->attrs[$m] = $args[0];
|
||||
|
||||
} else { // add
|
||||
$this->appendAttribute($m, $args[0], $args[1]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Special setter for element's attribute.
|
||||
* @return static
|
||||
*/
|
||||
final public function href(string $path, array $query = null)
|
||||
{
|
||||
if ($query) {
|
||||
$query = http_build_query($query, '', '&');
|
||||
if ($query !== '') {
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
}
|
||||
$this->attrs['href'] = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
|
||||
* @param mixed $value
|
||||
* @return static
|
||||
*/
|
||||
public function data(string $name, $value = null)
|
||||
{
|
||||
if (func_num_args() === 1) {
|
||||
$this->attrs['data'] = $name;
|
||||
} else {
|
||||
$this->attrs["data-$name"] = is_bool($value) ? json_encode($value) : $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets element's HTML content.
|
||||
* @param IHtmlString|string $html
|
||||
* @return static
|
||||
*/
|
||||
final public function setHtml($html)
|
||||
{
|
||||
$this->children = [(string) $html];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's HTML content.
|
||||
*/
|
||||
final public function getHtml(): string
|
||||
{
|
||||
return implode('', $this->children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets element's textual content.
|
||||
* @param IHtmlString|string|int|float $text
|
||||
* @return static
|
||||
*/
|
||||
final public function setText($text)
|
||||
{
|
||||
if (!$text instanceof IHtmlString) {
|
||||
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
$this->children = [(string) $text];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's textual content.
|
||||
*/
|
||||
final public function getText(): string
|
||||
{
|
||||
return self::htmlToText($this->getHtml());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new element's child.
|
||||
* @param IHtmlString|string $child Html node or raw HTML string
|
||||
* @return static
|
||||
*/
|
||||
final public function addHtml($child)
|
||||
{
|
||||
return $this->insert(null, $child);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends plain-text string to element content.
|
||||
* @param IHtmlString|string|int|float $text
|
||||
* @return static
|
||||
*/
|
||||
public function addText($text)
|
||||
{
|
||||
if (!$text instanceof IHtmlString) {
|
||||
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
return $this->insert(null, $text);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and adds a new Html child.
|
||||
* @param array|string $attrs element's attributes or raw HTML string
|
||||
* @return static created element
|
||||
*/
|
||||
final public function create(string $name, $attrs = null)
|
||||
{
|
||||
$this->insert(null, $child = static::el($name, $attrs));
|
||||
return $child;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts child node.
|
||||
* @param IHtmlString|string $child Html node or raw HTML string
|
||||
* @return static
|
||||
*/
|
||||
public function insert(?int $index, $child, bool $replace = false)
|
||||
{
|
||||
$child = $child instanceof self ? $child : (string) $child;
|
||||
if ($index === null) { // append
|
||||
$this->children[] = $child;
|
||||
|
||||
} else { // insert or replace
|
||||
array_splice($this->children, $index, $replace ? 1 : 0, [$child]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts (replaces) child node (\ArrayAccess implementation).
|
||||
* @param int|null $index position or null for appending
|
||||
* @param Html|string $child Html node or raw HTML string
|
||||
*/
|
||||
final public function offsetSet($index, $child): void
|
||||
{
|
||||
$this->insert($index, $child, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns child node (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
* @return static|string
|
||||
*/
|
||||
final public function offsetGet($index)
|
||||
{
|
||||
return $this->children[$index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exists child node? (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
*/
|
||||
final public function offsetExists($index): bool
|
||||
{
|
||||
return isset($this->children[$index]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes child node (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
*/
|
||||
public function offsetUnset($index): void
|
||||
{
|
||||
if (isset($this->children[$index])) {
|
||||
array_splice($this->children, $index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns children count.
|
||||
*/
|
||||
final public function count(): int
|
||||
{
|
||||
return count($this->children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all children.
|
||||
*/
|
||||
public function removeChildren(): void
|
||||
{
|
||||
$this->children = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates over elements.
|
||||
*/
|
||||
final public function getIterator(): \ArrayIterator
|
||||
{
|
||||
return new \ArrayIterator($this->children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all children.
|
||||
*/
|
||||
final public function getChildren(): array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders element's start tag, content and end tag.
|
||||
*/
|
||||
final public function render(int $indent = null): string
|
||||
{
|
||||
$s = $this->startTag();
|
||||
|
||||
if (!$this->isEmpty) {
|
||||
// add content
|
||||
if ($indent !== null) {
|
||||
$indent++;
|
||||
}
|
||||
foreach ($this->children as $child) {
|
||||
if ($child instanceof self) {
|
||||
$s .= $child->render($indent);
|
||||
} else {
|
||||
$s .= $child;
|
||||
}
|
||||
}
|
||||
|
||||
// add end tag
|
||||
$s .= $this->endTag();
|
||||
}
|
||||
|
||||
if ($indent !== null) {
|
||||
return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
final public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return $this->render();
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's start tag.
|
||||
*/
|
||||
final public function startTag(): string
|
||||
{
|
||||
return $this->name
|
||||
? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>')
|
||||
: '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's end tag.
|
||||
*/
|
||||
final public function endTag(): string
|
||||
{
|
||||
return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's attributes.
|
||||
* @internal
|
||||
*/
|
||||
final public function attributes(): string
|
||||
{
|
||||
if (!is_array($this->attrs)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$s = '';
|
||||
$attrs = $this->attrs;
|
||||
foreach ($attrs as $key => $value) {
|
||||
if ($value === null || $value === false) {
|
||||
continue;
|
||||
|
||||
} elseif ($value === true) {
|
||||
if (static::$xhtml) {
|
||||
$s .= ' ' . $key . '="' . $key . '"';
|
||||
} else {
|
||||
$s .= ' ' . $key;
|
||||
}
|
||||
continue;
|
||||
|
||||
} elseif (is_array($value)) {
|
||||
if (strncmp($key, 'data-', 5) === 0) {
|
||||
$value = Json::encode($value);
|
||||
|
||||
} else {
|
||||
$tmp = null;
|
||||
foreach ($value as $k => $v) {
|
||||
if ($v != null) { // intentionally ==, skip nulls & empty string
|
||||
// composite 'style' vs. 'others'
|
||||
$tmp[] = $v === true ? $k : (is_string($k) ? $k . ':' . $v : $v);
|
||||
}
|
||||
}
|
||||
if ($tmp === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
|
||||
}
|
||||
|
||||
} elseif (is_float($value)) {
|
||||
$value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
|
||||
|
||||
} else {
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
||||
$q = strpos($value, '"') === false ? '"' : "'";
|
||||
$s .= ' ' . $key . '=' . $q
|
||||
. str_replace(
|
||||
['&', $q, '<'],
|
||||
['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'],
|
||||
$value
|
||||
)
|
||||
. (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
|
||||
. $q;
|
||||
}
|
||||
|
||||
$s = str_replace('@', '@', $s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clones all children too.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->children as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
$this->children[$key] = clone $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
|
||||
interface IHtmlString
|
||||
{
|
||||
/**
|
||||
* Returns string in HTML format
|
||||
*/
|
||||
function __toString(): string;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Localization;
|
||||
|
||||
|
||||
/**
|
||||
* Translator adapter.
|
||||
*/
|
||||
interface ITranslator
|
||||
{
|
||||
/**
|
||||
* Translates the given string.
|
||||
* @param mixed $message
|
||||
* @param mixed ...$parameters
|
||||
*/
|
||||
function translate($message, ...$parameters): string;
|
||||
}
|
||||
+666
@@ -0,0 +1,666 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Basic manipulation with images.
|
||||
*
|
||||
* <code>
|
||||
* $image = Image::fromFile('nette.jpg');
|
||||
* $image->resize(150, 100);
|
||||
* $image->sharpen();
|
||||
* $image->send();
|
||||
* </code>
|
||||
*
|
||||
* @method void alphaBlending(bool $on)
|
||||
* @method void antialias(bool $on)
|
||||
* @method void arc($x, $y, $w, $h, $start, $end, $color)
|
||||
* @method void char(int $font, $x, $y, string $char, $color)
|
||||
* @method void charUp(int $font, $x, $y, string $char, $color)
|
||||
* @method int colorAllocate($red, $green, $blue)
|
||||
* @method int colorAllocateAlpha($red, $green, $blue, $alpha)
|
||||
* @method int colorAt($x, $y)
|
||||
* @method int colorClosest($red, $green, $blue)
|
||||
* @method int colorClosestAlpha($red, $green, $blue, $alpha)
|
||||
* @method int colorClosestHWB($red, $green, $blue)
|
||||
* @method void colorDeallocate($color)
|
||||
* @method int colorExact($red, $green, $blue)
|
||||
* @method int colorExactAlpha($red, $green, $blue, $alpha)
|
||||
* @method void colorMatch(Image $image2)
|
||||
* @method int colorResolve($red, $green, $blue)
|
||||
* @method int colorResolveAlpha($red, $green, $blue, $alpha)
|
||||
* @method void colorSet($index, $red, $green, $blue)
|
||||
* @method array colorsForIndex($index)
|
||||
* @method int colorsTotal()
|
||||
* @method int colorTransparent($color = null)
|
||||
* @method void convolution(array $matrix, float $div, float $offset)
|
||||
* @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
|
||||
* @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
|
||||
* @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
|
||||
* @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
|
||||
* @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
|
||||
* @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
|
||||
* @method void dashedLine($x1, $y1, $x2, $y2, $color)
|
||||
* @method void ellipse($cx, $cy, $w, $h, $color)
|
||||
* @method void fill($x, $y, $color)
|
||||
* @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
|
||||
* @method void filledEllipse($cx, $cy, $w, $h, $color)
|
||||
* @method void filledPolygon(array $points, $numPoints, $color)
|
||||
* @method void filledRectangle($x1, $y1, $x2, $y2, $color)
|
||||
* @method void fillToBorder($x, $y, $border, $color)
|
||||
* @method void filter($filtertype)
|
||||
* @method void flip(int $mode)
|
||||
* @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null)
|
||||
* @method void gammaCorrect(float $inputgamma, float $outputgamma)
|
||||
* @method array getClip()
|
||||
* @method int interlace($interlace = null)
|
||||
* @method bool isTrueColor()
|
||||
* @method void layerEffect($effect)
|
||||
* @method void line($x1, $y1, $x2, $y2, $color)
|
||||
* @method void openPolygon(array $points, int $num_points, int $color)
|
||||
* @method void paletteCopy(Image $source)
|
||||
* @method void paletteToTrueColor()
|
||||
* @method void polygon(array $points, $numPoints, $color)
|
||||
* @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null)
|
||||
* @method void rectangle($x1, $y1, $x2, $y2, $col)
|
||||
* @method mixed resolution(int $res_x = null, int $res_y = null)
|
||||
* @method Image rotate(float $angle, $backgroundColor)
|
||||
* @method void saveAlpha(bool $saveflag)
|
||||
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
|
||||
* @method void setBrush(Image $brush)
|
||||
* @method void setClip(int $x1, int $y1, int $x2, int $y2)
|
||||
* @method void setPixel($x, $y, $color)
|
||||
* @method void setStyle(array $style)
|
||||
* @method void setThickness($thickness)
|
||||
* @method void setTile(Image $tile)
|
||||
* @method void string($font, $x, $y, string $s, $col)
|
||||
* @method void stringUp($font, $x, $y, string $s, $col)
|
||||
* @method void trueColorToPalette(bool $dither, $ncolors)
|
||||
* @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
|
||||
* @property-read int $width
|
||||
* @property-read int $height
|
||||
* @property-read resource $imageResource
|
||||
*/
|
||||
class Image
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** {@link resize()} only shrinks images */
|
||||
public const SHRINK_ONLY = 0b0001;
|
||||
|
||||
/** {@link resize()} will ignore aspect ratio */
|
||||
public const STRETCH = 0b0010;
|
||||
|
||||
/** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
|
||||
public const FIT = 0b0000;
|
||||
|
||||
/** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
|
||||
public const FILL = 0b0100;
|
||||
|
||||
/** {@link resize()} fills given area exactly */
|
||||
public const EXACT = 0b1000;
|
||||
|
||||
/** image types */
|
||||
public const
|
||||
JPEG = IMAGETYPE_JPEG,
|
||||
PNG = IMAGETYPE_PNG,
|
||||
GIF = IMAGETYPE_GIF,
|
||||
WEBP = IMAGETYPE_WEBP,
|
||||
BMP = IMAGETYPE_BMP;
|
||||
|
||||
public const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
|
||||
|
||||
private const FORMATS = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::BMP => 'bmp'];
|
||||
|
||||
/** @var resource */
|
||||
private $image;
|
||||
|
||||
|
||||
/**
|
||||
* Returns RGB color (0..255) and transparency (0..127).
|
||||
*/
|
||||
public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
|
||||
{
|
||||
return [
|
||||
'red' => max(0, min(255, $red)),
|
||||
'green' => max(0, min(255, $green)),
|
||||
'blue' => max(0, min(255, $blue)),
|
||||
'alpha' => max(0, min(127, $transparency)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens image from file.
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws UnknownImageFileException if file not found or file type is not known
|
||||
* @return static
|
||||
*/
|
||||
public static function fromFile(string $file, int &$detectedFormat = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
$detectedFormat = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error
|
||||
if (!isset(self::FORMATS[$detectedFormat])) {
|
||||
$detectedFormat = null;
|
||||
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
|
||||
}
|
||||
return new static(Callback::invokeSafe('imagecreatefrom' . image_type_to_extension($detectedFormat, false), [$file], function (string $message): void {
|
||||
throw new ImageException($message);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new image from the image stream in the string.
|
||||
* @return static
|
||||
* @throws ImageException
|
||||
*/
|
||||
public static function fromString(string $s, int &$detectedFormat = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
$tmp = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error
|
||||
$detectedFormat = isset(self::FORMATS[$tmp]) ? $tmp : null;
|
||||
}
|
||||
|
||||
return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function (string $message): void {
|
||||
throw new ImageException($message);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates blank image.
|
||||
* @return static
|
||||
*/
|
||||
public static function fromBlank(int $width, int $height, array $color = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
if ($width < 1 || $height < 1) {
|
||||
throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
|
||||
}
|
||||
|
||||
$image = imagecreatetruecolor($width, $height);
|
||||
if ($color) {
|
||||
$color += ['alpha' => 0];
|
||||
$color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
|
||||
imagealphablending($image, false);
|
||||
imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
|
||||
imagealphablending($image, true);
|
||||
}
|
||||
return new static($image);
|
||||
}
|
||||
|
||||
|
||||
public static function typeToExtension(int $type): string
|
||||
{
|
||||
if (!isset(self::FORMATS[$type])) {
|
||||
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
|
||||
}
|
||||
return self::FORMATS[$type];
|
||||
}
|
||||
|
||||
|
||||
public static function typeToMimeType(int $type): string
|
||||
{
|
||||
return 'image/' . self::typeToExtension($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps GD image.
|
||||
* @param resource $image
|
||||
*/
|
||||
public function __construct($image)
|
||||
{
|
||||
$this->setImageResource($image);
|
||||
imagesavealpha($image, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns image width.
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return imagesx($this->image);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns image height.
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return imagesy($this->image);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets image resource.
|
||||
* @param resource $image
|
||||
* @return static
|
||||
*/
|
||||
protected function setImageResource($image)
|
||||
{
|
||||
if (!is_resource($image) || get_resource_type($image) !== 'gd') {
|
||||
throw new Nette\InvalidArgumentException('Image is not valid.');
|
||||
}
|
||||
$this->image = $image;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns image GD resource.
|
||||
* @return resource
|
||||
*/
|
||||
public function getImageResource()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resizes image.
|
||||
* @param int|string|null $width in pixels or percent
|
||||
* @param int|string|null $height in pixels or percent
|
||||
* @return static
|
||||
*/
|
||||
public function resize($width, $height, int $flags = self::FIT)
|
||||
{
|
||||
if ($flags & self::EXACT) {
|
||||
return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
|
||||
}
|
||||
|
||||
[$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
|
||||
|
||||
if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
|
||||
$newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource();
|
||||
imagecopyresampled(
|
||||
$newImage, $this->image,
|
||||
0, 0, 0, 0,
|
||||
$newWidth, $newHeight, $this->getWidth(), $this->getHeight()
|
||||
);
|
||||
$this->image = $newImage;
|
||||
}
|
||||
|
||||
if ($width < 0 || $height < 0) {
|
||||
imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates dimensions of resized image.
|
||||
* @param int|string|null $newWidth in pixels or percent
|
||||
* @param int|string|null $newHeight in pixels or percent
|
||||
*/
|
||||
public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $flags = self::FIT): array
|
||||
{
|
||||
if ($newWidth !== null && self::isPercent($newWidth)) {
|
||||
$newWidth = (int) round($srcWidth / 100 * abs($newWidth));
|
||||
$percents = true;
|
||||
} else {
|
||||
$newWidth = abs($newWidth);
|
||||
}
|
||||
|
||||
if ($newHeight !== null && self::isPercent($newHeight)) {
|
||||
$newHeight = (int) round($srcHeight / 100 * abs($newHeight));
|
||||
$flags |= empty($percents) ? 0 : self::STRETCH;
|
||||
} else {
|
||||
$newHeight = abs($newHeight);
|
||||
}
|
||||
|
||||
if ($flags & self::STRETCH) { // non-proportional
|
||||
if (!$newWidth || !$newHeight) {
|
||||
throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
|
||||
}
|
||||
|
||||
if ($flags & self::SHRINK_ONLY) {
|
||||
$newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth));
|
||||
$newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight));
|
||||
}
|
||||
|
||||
} else { // proportional
|
||||
if (!$newWidth && !$newHeight) {
|
||||
throw new Nette\InvalidArgumentException('At least width or height must be specified.');
|
||||
}
|
||||
|
||||
$scale = [];
|
||||
if ($newWidth > 0) { // fit width
|
||||
$scale[] = $newWidth / $srcWidth;
|
||||
}
|
||||
|
||||
if ($newHeight > 0) { // fit height
|
||||
$scale[] = $newHeight / $srcHeight;
|
||||
}
|
||||
|
||||
if ($flags & self::FILL) {
|
||||
$scale = [max($scale)];
|
||||
}
|
||||
|
||||
if ($flags & self::SHRINK_ONLY) {
|
||||
$scale[] = 1;
|
||||
}
|
||||
|
||||
$scale = min($scale);
|
||||
$newWidth = (int) round($srcWidth * $scale);
|
||||
$newHeight = (int) round($srcHeight * $scale);
|
||||
}
|
||||
|
||||
return [max($newWidth, 1), max($newHeight, 1)];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Crops image.
|
||||
* @param int|string $left in pixels or percent
|
||||
* @param int|string $top in pixels or percent
|
||||
* @param int|string $width in pixels or percent
|
||||
* @param int|string $height in pixels or percent
|
||||
* @return static
|
||||
*/
|
||||
public function crop($left, $top, $width, $height)
|
||||
{
|
||||
[$r['x'], $r['y'], $r['width'], $r['height']]
|
||||
= static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
|
||||
if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') {
|
||||
$this->image = imagecrop($this->image, $r);
|
||||
imagesavealpha($this->image, true);
|
||||
} else {
|
||||
$newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource();
|
||||
imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
|
||||
$this->image = $newImage;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates dimensions of cutout in image.
|
||||
* @param int|string $left in pixels or percent
|
||||
* @param int|string $top in pixels or percent
|
||||
* @param int|string $newWidth in pixels or percent
|
||||
* @param int|string $newHeight in pixels or percent
|
||||
*/
|
||||
public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array
|
||||
{
|
||||
if (self::isPercent($newWidth)) {
|
||||
$newWidth = (int) round($srcWidth / 100 * $newWidth);
|
||||
}
|
||||
if (self::isPercent($newHeight)) {
|
||||
$newHeight = (int) round($srcHeight / 100 * $newHeight);
|
||||
}
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($srcWidth - $newWidth) / 100 * $left);
|
||||
}
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($srcHeight - $newHeight) / 100 * $top);
|
||||
}
|
||||
if ($left < 0) {
|
||||
$newWidth += $left;
|
||||
$left = 0;
|
||||
}
|
||||
if ($top < 0) {
|
||||
$newHeight += $top;
|
||||
$top = 0;
|
||||
}
|
||||
$newWidth = min($newWidth, $srcWidth - $left);
|
||||
$newHeight = min($newHeight, $srcHeight - $top);
|
||||
return [$left, $top, $newWidth, $newHeight];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sharpen image.
|
||||
* @return static
|
||||
*/
|
||||
public function sharpen()
|
||||
{
|
||||
imageconvolution($this->image, [ // my magic numbers ;)
|
||||
[-1, -1, -1],
|
||||
[-1, 24, -1],
|
||||
[-1, -1, -1],
|
||||
], 16, 0);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Puts another image into this image.
|
||||
* @param int|string $left in pixels or percent
|
||||
* @param int|string $top in pixels or percent
|
||||
* @param int $opacity 0..100
|
||||
* @return static
|
||||
*/
|
||||
public function place(self $image, $left = 0, $top = 0, int $opacity = 100)
|
||||
{
|
||||
$opacity = max(0, min(100, $opacity));
|
||||
if ($opacity === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$width = $image->getWidth();
|
||||
$height = $image->getHeight();
|
||||
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($this->getWidth() - $width) / 100 * $left);
|
||||
}
|
||||
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($this->getHeight() - $height) / 100 * $top);
|
||||
}
|
||||
|
||||
$output = $input = $image->image;
|
||||
if ($opacity < 100) {
|
||||
$tbl = [];
|
||||
for ($i = 0; $i < 128; $i++) {
|
||||
$tbl[$i] = round(127 - (127 - $i) * $opacity / 100);
|
||||
}
|
||||
|
||||
$output = imagecreatetruecolor($width, $height);
|
||||
imagealphablending($output, false);
|
||||
if (!$image->isTrueColor()) {
|
||||
$input = $output;
|
||||
imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
|
||||
imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
|
||||
}
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$c = \imagecolorat($input, $x, $y);
|
||||
$c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24);
|
||||
\imagesetpixel($output, $x, $y, $c);
|
||||
}
|
||||
}
|
||||
imagealphablending($output, true);
|
||||
}
|
||||
|
||||
imagecopy(
|
||||
$this->image, $output,
|
||||
$left, $top, 0, 0, $width, $height
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves image to the file. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG.
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function save(string $file, int $quality = null, int $type = null): void
|
||||
{
|
||||
if ($type === null) {
|
||||
$extensions = array_flip(self::FORMATS) + ['jpg' => self::JPEG];
|
||||
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
if (!isset($extensions[$ext])) {
|
||||
throw new Nette\InvalidArgumentException("Unsupported file extension '$ext'.");
|
||||
}
|
||||
$type = $extensions[$ext];
|
||||
}
|
||||
|
||||
$this->output($type, $quality, $file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to string. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG.
|
||||
*/
|
||||
public function toString(int $type = self::JPEG, int $quality = null): string
|
||||
{
|
||||
return Helpers::capture(function () use ($type, $quality) {
|
||||
$this->output($type, $quality);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return $this->toString();
|
||||
} catch (\Throwable $e) {
|
||||
if (func_num_args() || PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to browser. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG.
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function send(int $type = self::JPEG, int $quality = null): void
|
||||
{
|
||||
header('Content-Type: ' . self::typeToMimeType($type));
|
||||
$this->output($type, $quality);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to browser or file.
|
||||
* @throws ImageException
|
||||
*/
|
||||
private function output(int $type, ?int $quality, string $file = null): void
|
||||
{
|
||||
switch ($type) {
|
||||
case self::JPEG:
|
||||
$quality = $quality === null ? 85 : max(0, min(100, $quality));
|
||||
$success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::PNG:
|
||||
$quality = $quality === null ? 9 : max(0, min(9, $quality));
|
||||
$success = @imagepng($this->image, $file, $quality); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::GIF:
|
||||
$success = @imagegif($this->image, $file); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::WEBP:
|
||||
$quality = $quality === null ? 80 : max(0, min(100, $quality));
|
||||
$success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::BMP:
|
||||
$success = @imagebmp($this->image, $file); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
|
||||
}
|
||||
if (!$success) {
|
||||
throw new ImageException(Helpers::getLastError() ?: 'Unknown error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call to undefined method.
|
||||
* @return mixed
|
||||
* @throws Nette\MemberAccessException
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$function = 'image' . $name;
|
||||
if (!function_exists($function)) {
|
||||
ObjectHelpers::strictCall(get_class($this), $name);
|
||||
}
|
||||
|
||||
foreach ($args as $key => $value) {
|
||||
if ($value instanceof self) {
|
||||
$args[$key] = $value->getImageResource();
|
||||
|
||||
} elseif (is_array($value) && isset($value['red'])) { // rgb
|
||||
$args[$key] = imagecolorallocatealpha(
|
||||
$this->image,
|
||||
$value['red'], $value['green'], $value['blue'], $value['alpha']
|
||||
) ?: imagecolorresolvealpha(
|
||||
$this->image,
|
||||
$value['red'], $value['green'], $value['blue'], $value['alpha']
|
||||
);
|
||||
}
|
||||
}
|
||||
$res = $function($this->image, ...$args);
|
||||
return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
|
||||
}
|
||||
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
ob_start(function () {});
|
||||
imagegd2($this->image);
|
||||
$this->setImageResource(imagecreatefromstring(ob_get_clean()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int|string $num in pixels or percent
|
||||
*/
|
||||
private static function isPercent(&$num): bool
|
||||
{
|
||||
if (is_string($num) && substr($num, -1) === '%') {
|
||||
$num = (float) substr($num, 0, -1);
|
||||
return true;
|
||||
} elseif (is_int($num) || $num === (string) (int) $num) {
|
||||
$num = (int) $num;
|
||||
return false;
|
||||
}
|
||||
throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevents serialization.
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.');
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* JSON encoder and decoder.
|
||||
*/
|
||||
final class Json
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
public const FORCE_ARRAY = 0b0001;
|
||||
|
||||
public const PRETTY = 0b0010;
|
||||
|
||||
public const ESCAPE_UNICODE = 0b0100;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the JSON representation of a value. Accepts flag Json::PRETTY.
|
||||
* @param mixed $value
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function encode($value, int $flags = 0): string
|
||||
{
|
||||
$flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE)
|
||||
| JSON_UNESCAPED_SLASHES
|
||||
| ($flags & self::PRETTY ? JSON_PRETTY_PRINT : 0)
|
||||
| (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7
|
||||
|
||||
$json = json_encode($value, $flags);
|
||||
if ($error = json_last_error()) {
|
||||
throw new JsonException(json_last_error_msg(), $error);
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a JSON string. Accepts flag Json::FORCE_ARRAY.
|
||||
* @return mixed
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function decode(string $json, int $flags = 0)
|
||||
{
|
||||
$forceArray = (bool) ($flags & self::FORCE_ARRAY);
|
||||
$value = json_decode($json, $forceArray, 512, JSON_BIGINT_AS_STRING);
|
||||
if ($error = json_last_error()) {
|
||||
throw new JsonException(json_last_error_msg(), $error);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use Nette\MemberAccessException;
|
||||
|
||||
|
||||
/**
|
||||
* Nette\SmartObject helpers.
|
||||
*/
|
||||
final class ObjectHelpers
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/** @throws MemberAccessException */
|
||||
public static function strictGet(string $class, string $name): void
|
||||
{
|
||||
$rc = new \ReflectionClass($class);
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
|
||||
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
|
||||
), $name);
|
||||
throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
|
||||
}
|
||||
|
||||
|
||||
/** @throws MemberAccessException */
|
||||
public static function strictSet(string $class, string $name): void
|
||||
{
|
||||
$rc = new \ReflectionClass($class);
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
|
||||
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
|
||||
), $name);
|
||||
throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
|
||||
}
|
||||
|
||||
|
||||
/** @throws MemberAccessException */
|
||||
public static function strictCall(string $class, string $method, array $additionalMethods = []): void
|
||||
{
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
get_class_methods($class),
|
||||
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
|
||||
$additionalMethods
|
||||
), $method);
|
||||
|
||||
if (method_exists($class, $method)) { // called parent::$method()
|
||||
$class = 'parent';
|
||||
}
|
||||
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
}
|
||||
|
||||
|
||||
/** @throws MemberAccessException */
|
||||
public static function strictStaticCall(string $class, string $method): void
|
||||
{
|
||||
$hint = self::getSuggestion(
|
||||
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
|
||||
$method
|
||||
);
|
||||
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns array of magic properties defined by annotation @property.
|
||||
* @return array of [name => bit mask]
|
||||
* @internal
|
||||
*/
|
||||
public static function getMagicProperties(string $class): array
|
||||
{
|
||||
static $cache;
|
||||
$props = &$cache[$class];
|
||||
if ($props !== null) {
|
||||
return $props;
|
||||
}
|
||||
|
||||
$rc = new \ReflectionClass($class);
|
||||
preg_match_all(
|
||||
'~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx',
|
||||
(string) $rc->getDocComment(), $matches, PREG_SET_ORDER
|
||||
);
|
||||
|
||||
$props = [];
|
||||
foreach ($matches as [, $type, $name]) {
|
||||
$uname = ucfirst($name);
|
||||
$write = $type !== '-read'
|
||||
&& $rc->hasMethod($nm = 'set' . $uname)
|
||||
&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
|
||||
$read = $type !== '-write'
|
||||
&& ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
|
||||
&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
|
||||
|
||||
if ($read || $write) {
|
||||
$props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rc->getTraits() as $trait) {
|
||||
$props += self::getMagicProperties($trait->name);
|
||||
}
|
||||
|
||||
if ($parent = get_parent_class($class)) {
|
||||
$props += self::getMagicProperties($parent);
|
||||
}
|
||||
return $props;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the best suggestion (for 8-bit encoding).
|
||||
* @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities
|
||||
* @internal
|
||||
*/
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
{
|
||||
$norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value);
|
||||
$best = null;
|
||||
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
||||
foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
|
||||
$item = $item instanceof \Reflector ? $item->name : $item;
|
||||
if ($item !== $value && (
|
||||
($len = levenshtein($item, $value, 10, 11, 10)) < $min
|
||||
|| ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min
|
||||
)) {
|
||||
$min = $len;
|
||||
$best = $item;
|
||||
}
|
||||
}
|
||||
return $best;
|
||||
}
|
||||
|
||||
|
||||
private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array
|
||||
{
|
||||
do {
|
||||
$doc[] = $rc->getDocComment();
|
||||
$traits = $rc->getTraits();
|
||||
while ($trait = array_pop($traits)) {
|
||||
$doc[] = $trait->getDocComment();
|
||||
$traits += $trait->getTraits();
|
||||
}
|
||||
} while ($rc = $rc->getParentClass());
|
||||
return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the public non-static property exists.
|
||||
* @return bool|string returns 'event' if the property exists and has event like name
|
||||
* @internal
|
||||
*/
|
||||
public static function hasProperty(string $class, string $name)
|
||||
{
|
||||
static $cache;
|
||||
$prop = &$cache[$class][$name];
|
||||
if ($prop === null) {
|
||||
$prop = false;
|
||||
try {
|
||||
$rp = new \ReflectionProperty($class, $name);
|
||||
if ($rp->isPublic() && !$rp->isStatic()) {
|
||||
$prop = $name >= 'onA' && $name < 'on_' ? 'event' : true;
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
}
|
||||
}
|
||||
return $prop;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Nette\Object behaviour mixin.
|
||||
* @deprecated
|
||||
*/
|
||||
final class ObjectMixin
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/** @deprecated use ObjectHelpers::getSuggestion() */
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
{
|
||||
trigger_error(__METHOD__ . '() has been renamed to Nette\Utils\ObjectHelpers::getSuggestion()', E_USER_DEPRECATED);
|
||||
return ObjectHelpers::getSuggestion($possibilities, $value);
|
||||
}
|
||||
|
||||
|
||||
public static function setExtensionMethod(): void
|
||||
{
|
||||
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
|
||||
public static function getExtensionMethod(): void
|
||||
{
|
||||
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Paginating math.
|
||||
*
|
||||
* @property int $page
|
||||
* @property-read int $firstPage
|
||||
* @property-read int|null $lastPage
|
||||
* @property int $base
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read int|null $pageCount
|
||||
* @property int $itemsPerPage
|
||||
* @property int|null $itemCount
|
||||
* @property-read int $offset
|
||||
* @property-read int|null $countdownOffset
|
||||
* @property-read int $length
|
||||
*/
|
||||
class Paginator
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
private $base = 1;
|
||||
|
||||
/** @var int */
|
||||
private $itemsPerPage = 1;
|
||||
|
||||
/** @var int */
|
||||
private $page = 1;
|
||||
|
||||
/** @var int|null */
|
||||
private $itemCount;
|
||||
|
||||
|
||||
/**
|
||||
* Sets current page number.
|
||||
* @return static
|
||||
*/
|
||||
public function setPage(int $page)
|
||||
{
|
||||
$this->page = $page;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns current page number.
|
||||
*/
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->base + $this->getPageIndex();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page number.
|
||||
*/
|
||||
public function getFirstPage(): int
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns last page number.
|
||||
*/
|
||||
public function getLastPage(): ?int
|
||||
{
|
||||
return $this->itemCount === null ? null : $this->base + max(0, $this->getPageCount() - 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets first page (base) number.
|
||||
* @return static
|
||||
*/
|
||||
public function setBase(int $base)
|
||||
{
|
||||
$this->base = $base;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page (base) number.
|
||||
*/
|
||||
public function getBase(): int
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns zero-based page number.
|
||||
*/
|
||||
protected function getPageIndex(): int
|
||||
{
|
||||
$index = max(0, $this->page - $this->base);
|
||||
return $this->itemCount === null ? $index : min($index, max(0, $this->getPageCount() - 1));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the first one?
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
return $this->getPageIndex() === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the last one?
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return $this->itemCount === null ? false : $this->getPageIndex() >= $this->getPageCount() - 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the total number of pages.
|
||||
*/
|
||||
public function getPageCount(): ?int
|
||||
{
|
||||
return $this->itemCount === null ? null : (int) ceil($this->itemCount / $this->itemsPerPage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of items to display on a single page.
|
||||
* @return static
|
||||
*/
|
||||
public function setItemsPerPage(int $itemsPerPage)
|
||||
{
|
||||
$this->itemsPerPage = max(1, $itemsPerPage);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of items to display on a single page.
|
||||
*/
|
||||
public function getItemsPerPage(): int
|
||||
{
|
||||
return $this->itemsPerPage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the total number of items.
|
||||
* @return static
|
||||
*/
|
||||
public function setItemCount(int $itemCount = null)
|
||||
{
|
||||
$this->itemCount = $itemCount === null ? null : max(0, $itemCount);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the total number of items.
|
||||
*/
|
||||
public function getItemCount(): ?int
|
||||
{
|
||||
return $this->itemCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute index of the first item on current page.
|
||||
*/
|
||||
public function getOffset(): int
|
||||
{
|
||||
return $this->getPageIndex() * $this->itemsPerPage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute index of the first item on current page in countdown paging.
|
||||
*/
|
||||
public function getCountdownOffset(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of items on current page.
|
||||
*/
|
||||
public function getLength(): int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? $this->itemsPerPage
|
||||
: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Secure random string generator.
|
||||
*/
|
||||
final class Random
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Generate random string.
|
||||
*/
|
||||
public static function generate(int $length = 10, string $charlist = '0-9a-z'): string
|
||||
{
|
||||
$charlist = count_chars(preg_replace_callback('#.-.#', function (array $m): string {
|
||||
return implode('', range($m[0][0], $m[0][2]));
|
||||
}, $charlist), 3);
|
||||
$chLen = strlen($charlist);
|
||||
|
||||
if ($length < 1) {
|
||||
throw new Nette\InvalidArgumentException('Length must be greater than zero.');
|
||||
} elseif ($chLen < 2) {
|
||||
throw new Nette\InvalidArgumentException('Character list must contain at least two chars.');
|
||||
}
|
||||
|
||||
$res = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$res .= $charlist[random_int(0, $chLen - 1)];
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP reflection helpers.
|
||||
*/
|
||||
final class Reflection
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
private const BUILTIN_TYPES = [
|
||||
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
|
||||
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1,
|
||||
];
|
||||
|
||||
|
||||
public static function isBuiltinType(string $type): bool
|
||||
{
|
||||
return isset(self::BUILTIN_TYPES[strtolower($type)]);
|
||||
}
|
||||
|
||||
|
||||
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
|
||||
{
|
||||
$type = $func->getReturnType();
|
||||
return $type instanceof \ReflectionNamedType && $func instanceof \ReflectionMethod
|
||||
? self::normalizeType($type->getName(), $func)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
public static function getParameterType(\ReflectionParameter $param): ?string
|
||||
{
|
||||
$type = $param->getType();
|
||||
return $type instanceof \ReflectionNamedType
|
||||
? self::normalizeType($type->getName(), $param)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
public static function getPropertyType(\ReflectionProperty $prop): ?string
|
||||
{
|
||||
$type = PHP_VERSION_ID >= 70400 ? $prop->getType() : null;
|
||||
return $type instanceof \ReflectionNamedType
|
||||
? self::normalizeType($type->getName(), $prop)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
*/
|
||||
private static function normalizeType(string $type, $reflection): string
|
||||
{
|
||||
$lower = strtolower($type);
|
||||
if ($lower === 'self') {
|
||||
return $reflection->getDeclaringClass()->name;
|
||||
} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
|
||||
return $reflection->getDeclaringClass()->getParentClass()->name;
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws \ReflectionException when default value is not available or resolvable
|
||||
*/
|
||||
public static function getParameterDefaultValue(\ReflectionParameter $param)
|
||||
{
|
||||
if ($param->isDefaultValueConstant()) {
|
||||
$const = $orig = $param->getDefaultValueConstantName();
|
||||
$pair = explode('::', $const);
|
||||
if (isset($pair[1])) {
|
||||
$pair[0] = self::normalizeType($pair[0], $param);
|
||||
try {
|
||||
$rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
|
||||
} catch (\ReflectionException $e) {
|
||||
$name = self::toString($param);
|
||||
throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e);
|
||||
}
|
||||
return $rcc->getValue();
|
||||
|
||||
} elseif (!defined($const)) {
|
||||
$const = substr((string) strrchr($const, '\\'), 1);
|
||||
if (!defined($const)) {
|
||||
$name = self::toString($param);
|
||||
throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.");
|
||||
}
|
||||
}
|
||||
return constant($const);
|
||||
}
|
||||
return $param->getDefaultValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns declaring class or trait.
|
||||
*/
|
||||
public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
|
||||
{
|
||||
foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
|
||||
if ($trait->hasProperty($prop->name)
|
||||
// doc-comment guessing as workaround for insufficient PHP reflection
|
||||
&& $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment()
|
||||
) {
|
||||
return self::getPropertyDeclaringClass($trait->getProperty($prop->name));
|
||||
}
|
||||
}
|
||||
return $prop->getDeclaringClass();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns declaring method in class or trait.
|
||||
*/
|
||||
public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod
|
||||
{
|
||||
// file & line guessing as workaround for insufficient PHP reflection
|
||||
$decl = $method->getDeclaringClass();
|
||||
if ($decl->getFileName() === $method->getFileName()
|
||||
&& $decl->getStartLine() <= $method->getStartLine()
|
||||
&& $decl->getEndLine() >= $method->getEndLine()
|
||||
) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
$hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()];
|
||||
if (($alias = $decl->getTraitAliases()[$method->name] ?? null)
|
||||
&& ($m = new \ReflectionMethod($alias))
|
||||
&& $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
|
||||
) {
|
||||
return self::getMethodDeclaringMethod($m);
|
||||
}
|
||||
|
||||
foreach ($decl->getTraits() as $trait) {
|
||||
if ($trait->hasMethod($method->name)
|
||||
&& ($m = $trait->getMethod($method->name))
|
||||
&& $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
|
||||
) {
|
||||
return self::getMethodDeclaringMethod($m);
|
||||
}
|
||||
}
|
||||
return $method;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Are documentation comments available?
|
||||
*/
|
||||
public static function areCommentsAvailable(): bool
|
||||
{
|
||||
static $res;
|
||||
return $res === null
|
||||
? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment()
|
||||
: $res;
|
||||
}
|
||||
|
||||
|
||||
public static function toString(\Reflector $ref): string
|
||||
{
|
||||
if ($ref instanceof \ReflectionClass) {
|
||||
return $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionMethod) {
|
||||
return $ref->getDeclaringClass()->name . '::' . $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionFunction) {
|
||||
return $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionProperty) {
|
||||
return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionParameter) {
|
||||
return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction()) . '()';
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expands class name into full name.
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function expandClassName(string $name, \ReflectionClass $rc): string
|
||||
{
|
||||
$lower = strtolower($name);
|
||||
if (empty($name)) {
|
||||
throw new Nette\InvalidArgumentException('Class name must not be empty.');
|
||||
|
||||
} elseif (isset(self::BUILTIN_TYPES[$lower])) {
|
||||
return $lower;
|
||||
|
||||
} elseif ($lower === 'self') {
|
||||
return $rc->name;
|
||||
|
||||
} elseif ($name[0] === '\\') { // fully qualified name
|
||||
return ltrim($name, '\\');
|
||||
}
|
||||
|
||||
$uses = self::getUseStatements($rc);
|
||||
$parts = explode('\\', $name, 2);
|
||||
if (isset($uses[$parts[0]])) {
|
||||
$parts[0] = $uses[$parts[0]];
|
||||
return implode('\\', $parts);
|
||||
|
||||
} elseif ($rc->inNamespace()) {
|
||||
return $rc->getNamespaceName() . '\\' . $name;
|
||||
|
||||
} else {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @return array of [alias => class] */
|
||||
public static function getUseStatements(\ReflectionClass $class): array
|
||||
{
|
||||
if ($class->isAnonymous()) {
|
||||
throw new Nette\NotImplementedException('Anonymous classes are not supported.');
|
||||
}
|
||||
static $cache = [];
|
||||
if (!isset($cache[$name = $class->name])) {
|
||||
if ($class->isInternal()) {
|
||||
$cache[$name] = [];
|
||||
} else {
|
||||
$code = file_get_contents($class->getFileName());
|
||||
$cache = self::parseUseStatements($code, $name) + $cache;
|
||||
}
|
||||
}
|
||||
return $cache[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses PHP code to [class => [alias => class, ...]]
|
||||
*/
|
||||
private static function parseUseStatements(string $code, string $forClass = null): array
|
||||
{
|
||||
try {
|
||||
$tokens = token_get_all($code, TOKEN_PARSE);
|
||||
} catch (\ParseError $e) {
|
||||
trigger_error($e->getMessage(), E_USER_NOTICE);
|
||||
$tokens = [];
|
||||
}
|
||||
$namespace = $class = $classLevel = $level = null;
|
||||
$res = $uses = [];
|
||||
|
||||
while ($token = current($tokens)) {
|
||||
next($tokens);
|
||||
switch (is_array($token) ? $token[0] : $token) {
|
||||
case T_NAMESPACE:
|
||||
$namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
|
||||
$uses = [];
|
||||
break;
|
||||
|
||||
case T_CLASS:
|
||||
case T_INTERFACE:
|
||||
case T_TRAIT:
|
||||
if ($name = self::fetch($tokens, T_STRING)) {
|
||||
$class = $namespace . $name;
|
||||
$classLevel = $level + 1;
|
||||
$res[$class] = $uses;
|
||||
if ($class === $forClass) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case T_USE:
|
||||
while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
|
||||
$name = ltrim($name, '\\');
|
||||
if (self::fetch($tokens, '{')) {
|
||||
while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
|
||||
if (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
|
||||
} else {
|
||||
$tmp = explode('\\', $suffix);
|
||||
$uses[end($tmp)] = $name . $suffix;
|
||||
}
|
||||
if (!self::fetch($tokens, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name;
|
||||
|
||||
} else {
|
||||
$tmp = explode('\\', $name);
|
||||
$uses[end($tmp)] = $name;
|
||||
}
|
||||
if (!self::fetch($tokens, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case T_CURLY_OPEN:
|
||||
case T_DOLLAR_OPEN_CURLY_BRACES:
|
||||
case '{':
|
||||
$level++;
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if ($level === $classLevel) {
|
||||
$class = $classLevel = null;
|
||||
}
|
||||
$level--;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
private static function fetch(array &$tokens, $take): ?string
|
||||
{
|
||||
$res = null;
|
||||
while ($token = current($tokens)) {
|
||||
[$token, $s] = is_array($token) ? $token : [$token, $token];
|
||||
if (in_array($token, (array) $take, true)) {
|
||||
$res .= $s;
|
||||
} elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
|
||||
break;
|
||||
}
|
||||
next($tokens);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
use Nette\Utils\ObjectHelpers;
|
||||
|
||||
|
||||
/**
|
||||
* Strict class for better experience.
|
||||
* - 'did you mean' hints
|
||||
* - access to undeclared members throws exceptions
|
||||
* - support for @property annotations
|
||||
* - support for calling event handlers stored in $onEvent via onEvent()
|
||||
*/
|
||||
trait SmartObject
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$class = get_class($this);
|
||||
|
||||
if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers
|
||||
if (is_iterable($this->$name)) {
|
||||
foreach ($this->$name as $handler) {
|
||||
$handler(...$args);
|
||||
}
|
||||
} elseif ($this->$name !== null) {
|
||||
throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($this->$name) . ' given.');
|
||||
}
|
||||
|
||||
} else {
|
||||
ObjectHelpers::strictCall($class, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args)
|
||||
{
|
||||
ObjectHelpers::strictStaticCall(static::class, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws MemberAccessException if the property is not defined.
|
||||
*/
|
||||
public function &__get(string $name)
|
||||
{
|
||||
$class = get_class($this);
|
||||
|
||||
if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter
|
||||
if (!($prop & 0b0001)) {
|
||||
throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
|
||||
}
|
||||
$m = ($prop & 0b0010 ? 'get' : 'is') . $name;
|
||||
if ($prop & 0b0100) { // return by reference
|
||||
return $this->$m();
|
||||
} else {
|
||||
$val = $this->$m();
|
||||
return $val;
|
||||
}
|
||||
} else {
|
||||
ObjectHelpers::strictGet($class, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @throws MemberAccessException if the property is not defined or is read-only
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
$class = get_class($this);
|
||||
|
||||
if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property
|
||||
$this->$name = $value;
|
||||
|
||||
} elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter
|
||||
if (!($prop & 0b1000)) {
|
||||
throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
|
||||
}
|
||||
$this->{'set' . $name}($value);
|
||||
|
||||
} else {
|
||||
ObjectHelpers::strictSet($class, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public function __unset(string $name)
|
||||
{
|
||||
$class = get_class($this);
|
||||
if (!ObjectHelpers::hasProperty($class, $name)) {
|
||||
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return isset(ObjectHelpers::getMagicProperties(get_class($this))[$name]);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Static class.
|
||||
*/
|
||||
trait StaticClass
|
||||
{
|
||||
/** @throws \Error */
|
||||
final public function __construct()
|
||||
{
|
||||
throw new \Error('Class ' . get_class($this) . ' is static and cannot be instantiated.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call to undefined static method.
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args)
|
||||
{
|
||||
Utils\ObjectHelpers::strictStaticCall(static::class, $name);
|
||||
}
|
||||
}
|
||||
+532
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function is_array, is_object, strlen;
|
||||
|
||||
|
||||
/**
|
||||
* String tools library.
|
||||
*/
|
||||
class Strings
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
public const TRIM_CHARACTERS = " \t\n\r\0\x0B\u{A0}";
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the string is valid for UTF-8 encoding.
|
||||
*/
|
||||
public static function checkEncoding(string $s): bool
|
||||
{
|
||||
return $s === self::fixEncoding($s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes invalid code unit sequences from UTF-8 string.
|
||||
*/
|
||||
public static function fixEncoding(string $s): string
|
||||
{
|
||||
// removes xD800-xDFFF, x110000 and higher
|
||||
return htmlspecialchars_decode(htmlspecialchars($s, ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'), ENT_NOQUOTES);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a specific character in UTF-8 from code point (0x0 to 0xD7FF or 0xE000 to 0x10FFFF).
|
||||
* @throws Nette\InvalidArgumentException if code point is not in valid range
|
||||
*/
|
||||
public static function chr(int $code): string
|
||||
{
|
||||
if ($code < 0 || ($code >= 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) {
|
||||
throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.');
|
||||
} elseif (!extension_loaded('iconv')) {
|
||||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the $haystack string with the prefix $needle?
|
||||
*/
|
||||
public static function startsWith(string $haystack, string $needle): bool
|
||||
{
|
||||
return strncmp($haystack, $needle, strlen($needle)) === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ends the $haystack string with the suffix $needle?
|
||||
*/
|
||||
public static function endsWith(string $haystack, string $needle): bool
|
||||
{
|
||||
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does $haystack contain $needle?
|
||||
*/
|
||||
public static function contains(string $haystack, string $needle): bool
|
||||
{
|
||||
return strpos($haystack, $needle) !== false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a part of UTF-8 string.
|
||||
*/
|
||||
public static function substring(string $s, int $start, int $length = null): string
|
||||
{
|
||||
if (function_exists('mb_substr')) {
|
||||
return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster
|
||||
} elseif (!extension_loaded('iconv')) {
|
||||
throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.');
|
||||
} elseif ($length === null) {
|
||||
$length = self::length($s);
|
||||
} elseif ($start < 0 && $length < 0) {
|
||||
$start += self::length($s); // unifies iconv_substr behavior with mb_substr
|
||||
}
|
||||
return iconv_substr($s, $start, $length, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes special controls characters and normalizes line endings, spaces and normal form to NFC in UTF-8 string.
|
||||
*/
|
||||
public static function normalize(string $s): string
|
||||
{
|
||||
// convert to compressed normal form (NFC)
|
||||
if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) {
|
||||
$s = $n;
|
||||
}
|
||||
|
||||
$s = self::normalizeNewLines($s);
|
||||
|
||||
// remove control characters; leave \t + \n
|
||||
$s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]);
|
||||
|
||||
// right trim
|
||||
$s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]);
|
||||
|
||||
// leading and trailing blank lines
|
||||
$s = trim($s, "\n");
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Standardize line endings to unix-like.
|
||||
*/
|
||||
public static function normalizeNewLines(string $s): string
|
||||
{
|
||||
return str_replace(["\r\n", "\r"], "\n", $s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to ASCII.
|
||||
*/
|
||||
public static function toAscii(string $s): string
|
||||
{
|
||||
$iconv = defined('ICONV_IMPL') ? ICONV_IMPL : null;
|
||||
static $transliterator = null;
|
||||
if ($transliterator === null && class_exists('Transliterator', false)) {
|
||||
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
|
||||
}
|
||||
|
||||
// remove control characters and check UTF-8 validity
|
||||
$s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]);
|
||||
|
||||
// transliteration (by Transliterator and iconv) is not optimal, replace some characters directly
|
||||
$s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю
|
||||
if ($iconv !== 'libiconv') {
|
||||
$s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔
|
||||
}
|
||||
|
||||
if ($transliterator) {
|
||||
$s = $transliterator->transliterate($s);
|
||||
// use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ
|
||||
if ($iconv === 'glibc') {
|
||||
$s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
$s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters
|
||||
} elseif ($iconv === 'libiconv') {
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
} else { // null or 'unknown' (#216)
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
|
||||
}
|
||||
} elseif ($iconv === 'glibc' || $iconv === 'libiconv') {
|
||||
// temporarily hide these characters to distinguish them from the garbage that iconv creates
|
||||
$s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06");
|
||||
if ($iconv === 'glibc') {
|
||||
// glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved
|
||||
$s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s);
|
||||
$s = strtr($s,
|
||||
"\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
|
||||
'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.');
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]);
|
||||
} else {
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
}
|
||||
// remove garbage that iconv creates during transliteration (eg Ý -> Y')
|
||||
$s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s);
|
||||
// restore temporarily hidden characters
|
||||
$s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?');
|
||||
} else {
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to web safe characters [a-z0-9-] text.
|
||||
*/
|
||||
public static function webalize(string $s, string $charlist = null, bool $lower = true): string
|
||||
{
|
||||
$s = self::toAscii($s);
|
||||
if ($lower) {
|
||||
$s = strtolower($s);
|
||||
}
|
||||
$s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]);
|
||||
$s = trim($s, '-');
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Truncates UTF-8 string to maximal length.
|
||||
*/
|
||||
public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string
|
||||
{
|
||||
if (self::length($s) > $maxLen) {
|
||||
$maxLen -= self::length($append);
|
||||
if ($maxLen < 1) {
|
||||
return $append;
|
||||
|
||||
} elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) {
|
||||
return $matches[0] . $append;
|
||||
|
||||
} else {
|
||||
return self::substring($s, 0, $maxLen) . $append;
|
||||
}
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indents UTF-8 string from the left.
|
||||
*/
|
||||
public static function indent(string $s, int $level = 1, string $chars = "\t"): string
|
||||
{
|
||||
if ($level > 0) {
|
||||
$s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level));
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to lower case.
|
||||
*/
|
||||
public static function lower(string $s): string
|
||||
{
|
||||
return mb_strtolower($s, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts first character to lower case.
|
||||
*/
|
||||
public static function firstLower(string $s): string
|
||||
{
|
||||
return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to upper case.
|
||||
*/
|
||||
public static function upper(string $s): string
|
||||
{
|
||||
return mb_strtoupper($s, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts first character to upper case.
|
||||
*/
|
||||
public static function firstUpper(string $s): string
|
||||
{
|
||||
return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Capitalizes UTF-8 string.
|
||||
*/
|
||||
public static function capitalize(string $s): string
|
||||
{
|
||||
return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Case-insensitive compares UTF-8 strings.
|
||||
*/
|
||||
public static function compare(string $left, string $right, int $len = null): bool
|
||||
{
|
||||
if (class_exists('Normalizer', false)) {
|
||||
$left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster
|
||||
$right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster
|
||||
}
|
||||
|
||||
if ($len < 0) {
|
||||
$left = self::substring($left, $len, -$len);
|
||||
$right = self::substring($right, $len, -$len);
|
||||
} elseif ($len !== null) {
|
||||
$left = self::substring($left, 0, $len);
|
||||
$right = self::substring($right, 0, $len);
|
||||
}
|
||||
return self::lower($left) === self::lower($right);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the length of common prefix of strings.
|
||||
* @param string[] $strings
|
||||
*/
|
||||
public static function findPrefix(array $strings): string
|
||||
{
|
||||
$first = array_shift($strings);
|
||||
for ($i = 0; $i < strlen($first); $i++) {
|
||||
foreach ($strings as $s) {
|
||||
if (!isset($s[$i]) || $first[$i] !== $s[$i]) {
|
||||
while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") {
|
||||
$i--;
|
||||
}
|
||||
return substr($first, 0, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $first;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns number of characters (not bytes) in UTF-8 string.
|
||||
* That is the number of Unicode code points which may differ from the number of graphemes.
|
||||
*/
|
||||
public static function length(string $s): int
|
||||
{
|
||||
return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strips whitespace from UTF-8 string.
|
||||
*/
|
||||
public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string
|
||||
{
|
||||
$charlist = preg_quote($charlist, '#');
|
||||
return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pad a UTF-8 string to a certain length with another string.
|
||||
*/
|
||||
public static function padLeft(string $s, int $length, string $pad = ' '): string
|
||||
{
|
||||
$length = max(0, $length - self::length($s));
|
||||
$padLen = self::length($pad);
|
||||
return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pad a UTF-8 string to a certain length with another string.
|
||||
*/
|
||||
public static function padRight(string $s, int $length, string $pad = ' '): string
|
||||
{
|
||||
$length = max(0, $length - self::length($s));
|
||||
$padLen = self::length($pad);
|
||||
return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverse string.
|
||||
*/
|
||||
public static function reverse(string $s): string
|
||||
{
|
||||
if (!extension_loaded('iconv')) {
|
||||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns part of $haystack before $nth occurence of $needle (negative value means searching from the end).
|
||||
* @return string|null returns null if the needle was not found
|
||||
*/
|
||||
public static function before(string $haystack, string $needle, int $nth = 1): ?string
|
||||
{
|
||||
$pos = self::pos($haystack, $needle, $nth);
|
||||
return $pos === null
|
||||
? null
|
||||
: substr($haystack, 0, $pos);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns part of $haystack after $nth occurence of $needle (negative value means searching from the end).
|
||||
* @return string|null returns null if the needle was not found
|
||||
*/
|
||||
public static function after(string $haystack, string $needle, int $nth = 1): ?string
|
||||
{
|
||||
$pos = self::pos($haystack, $needle, $nth);
|
||||
return $pos === null
|
||||
? null
|
||||
: substr($haystack, $pos + strlen($needle));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns position of $nth occurence of $needle in $haystack (negative value means searching from the end).
|
||||
* @return int|null offset in characters or null if the needle was not found
|
||||
*/
|
||||
public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int
|
||||
{
|
||||
$pos = self::pos($haystack, $needle, $nth);
|
||||
return $pos === null
|
||||
? null
|
||||
: self::length(substr($haystack, 0, $pos));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns position of $nth occurence of $needle in $haystack.
|
||||
* @return int|null offset in bytes or null if the needle was not found
|
||||
*/
|
||||
private static function pos(string $haystack, string $needle, int $nth = 1): ?int
|
||||
{
|
||||
if (!$nth) {
|
||||
return null;
|
||||
} elseif ($nth > 0) {
|
||||
if ($needle === '') {
|
||||
return 0;
|
||||
}
|
||||
$pos = 0;
|
||||
while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) {
|
||||
$pos++;
|
||||
}
|
||||
} else {
|
||||
$len = strlen($haystack);
|
||||
if ($needle === '') {
|
||||
return $len;
|
||||
}
|
||||
$pos = $len - 1;
|
||||
while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) {
|
||||
$pos--;
|
||||
}
|
||||
}
|
||||
return Helpers::falseToNull($pos);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Splits string by a regular expression.
|
||||
*/
|
||||
public static function split(string $subject, string $pattern, int $flags = 0): array
|
||||
{
|
||||
return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs a regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes).
|
||||
*/
|
||||
public static function match(string $subject, string $pattern, int $flags = 0, int $offset = 0): ?array
|
||||
{
|
||||
if ($offset > strlen($subject)) {
|
||||
return null;
|
||||
}
|
||||
return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])
|
||||
? $m
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs a global regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes), PREG_SET_ORDER is default.
|
||||
*/
|
||||
public static function matchAll(string $subject, string $pattern, int $flags = 0, int $offset = 0): array
|
||||
{
|
||||
if ($offset > strlen($subject)) {
|
||||
return [];
|
||||
}
|
||||
self::pcre('preg_match_all', [
|
||||
$pattern, $subject, &$m,
|
||||
($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
|
||||
$offset,
|
||||
]);
|
||||
return $m;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform a regular expression search and replace.
|
||||
* @param string|array $pattern
|
||||
* @param string|callable $replacement
|
||||
*/
|
||||
public static function replace(string $subject, $pattern, $replacement = null, int $limit = -1): string
|
||||
{
|
||||
if (is_object($replacement) || is_array($replacement)) {
|
||||
if (!is_callable($replacement, false, $textual)) {
|
||||
throw new Nette\InvalidStateException("Callback '$textual' is not callable.");
|
||||
}
|
||||
return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]);
|
||||
|
||||
} elseif ($replacement === null && is_array($pattern)) {
|
||||
$replacement = array_values($pattern);
|
||||
$pattern = array_keys($pattern);
|
||||
}
|
||||
|
||||
return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function pcre(string $func, array $args)
|
||||
{
|
||||
$res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void {
|
||||
// compile-time error, not detectable by preg_last_error
|
||||
throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0]));
|
||||
});
|
||||
|
||||
if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
|
||||
&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true))
|
||||
) {
|
||||
throw new RegexpException((RegexpException::MESSAGES[$code] ?? 'Unknown error')
|
||||
. ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Validation utilities.
|
||||
*/
|
||||
class Validators
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/** @var array<string,?callable> */
|
||||
protected static $validators = [
|
||||
// PHP types
|
||||
'array' => 'is_array',
|
||||
'bool' => 'is_bool',
|
||||
'boolean' => 'is_bool',
|
||||
'float' => 'is_float',
|
||||
'int' => 'is_int',
|
||||
'integer' => 'is_int',
|
||||
'null' => 'is_null',
|
||||
'object' => 'is_object',
|
||||
'resource' => 'is_resource',
|
||||
'scalar' => 'is_scalar',
|
||||
'string' => 'is_string',
|
||||
|
||||
// pseudo-types
|
||||
'callable' => [__CLASS__, 'isCallable'],
|
||||
'iterable' => 'is_iterable',
|
||||
'list' => [Arrays::class, 'isList'],
|
||||
'mixed' => [__CLASS__, 'isMixed'],
|
||||
'none' => [__CLASS__, 'isNone'],
|
||||
'number' => [__CLASS__, 'isNumber'],
|
||||
'numeric' => [__CLASS__, 'isNumeric'],
|
||||
'numericint' => [__CLASS__, 'isNumericInt'],
|
||||
|
||||
// string patterns
|
||||
'alnum' => 'ctype_alnum',
|
||||
'alpha' => 'ctype_alpha',
|
||||
'digit' => 'ctype_digit',
|
||||
'lower' => 'ctype_lower',
|
||||
'pattern' => null,
|
||||
'space' => 'ctype_space',
|
||||
'unicode' => [__CLASS__, 'isUnicode'],
|
||||
'upper' => 'ctype_upper',
|
||||
'xdigit' => 'ctype_xdigit',
|
||||
|
||||
// syntax validation
|
||||
'email' => [__CLASS__, 'isEmail'],
|
||||
'identifier' => [__CLASS__, 'isPhpIdentifier'],
|
||||
'uri' => [__CLASS__, 'isUri'],
|
||||
'url' => [__CLASS__, 'isUrl'],
|
||||
|
||||
// environment validation
|
||||
'class' => 'class_exists',
|
||||
'interface' => 'interface_exists',
|
||||
'directory' => 'is_dir',
|
||||
'file' => 'is_file',
|
||||
'type' => [__CLASS__, 'isType'],
|
||||
];
|
||||
|
||||
/** @var array<string,callable> */
|
||||
protected static $counters = [
|
||||
'string' => 'strlen',
|
||||
'unicode' => [Strings::class, 'length'],
|
||||
'array' => 'count',
|
||||
'list' => 'count',
|
||||
'alnum' => 'strlen',
|
||||
'alpha' => 'strlen',
|
||||
'digit' => 'strlen',
|
||||
'lower' => 'strlen',
|
||||
'space' => 'strlen',
|
||||
'upper' => 'strlen',
|
||||
'xdigit' => 'strlen',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Throws exception if a variable is of unexpected type (separated by pipe).
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assert($value, string $expected, string $label = 'variable'): void
|
||||
{
|
||||
if (!static::is($value, $expected)) {
|
||||
$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
|
||||
static $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'];
|
||||
$type = $translate[gettype($value)] ?? gettype($value);
|
||||
if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) {
|
||||
$type .= ' ' . var_export($value, true);
|
||||
} elseif (is_object($value)) {
|
||||
$type .= ' ' . get_class($value);
|
||||
}
|
||||
throw new AssertionException("The $label expects to be $expected, $type given.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throws exception if an array field is missing or of unexpected type (separated by pipe).
|
||||
* @param mixed[] $arr
|
||||
* @param int|string $field
|
||||
*/
|
||||
public static function assertField(array $arr, $field, string $expected = null, string $label = "item '%' in array"): void
|
||||
{
|
||||
if (!array_key_exists($field, $arr)) {
|
||||
throw new AssertionException('Missing ' . str_replace('%', $field, $label) . '.');
|
||||
|
||||
} elseif ($expected) {
|
||||
static::assert($arr[$field], $expected, str_replace('%', $field, $label));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a variable is of expected type (separated by pipe).
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function is($value, string $expected): bool
|
||||
{
|
||||
foreach (explode('|', $expected) as $item) {
|
||||
if (substr($item, -2) === '[]') {
|
||||
if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
} elseif (substr($item, 0, 1) === '?') {
|
||||
$item = substr($item, 1);
|
||||
if ($value === null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[$type] = $item = explode(':', $item, 2);
|
||||
if (isset(static::$validators[$type])) {
|
||||
try {
|
||||
if (!static::$validators[$type]($value)) {
|
||||
continue;
|
||||
}
|
||||
} catch (\TypeError $e) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($type === 'pattern') {
|
||||
if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
} elseif (!$value instanceof $type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($item[1])) {
|
||||
$length = $value;
|
||||
if (isset(static::$counters[$type])) {
|
||||
$length = static::$counters[$type]($value);
|
||||
}
|
||||
$range = explode('..', $item[1]);
|
||||
if (!isset($range[1])) {
|
||||
$range[1] = $range[0];
|
||||
}
|
||||
if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether all values are of expected type (separated by pipe).
|
||||
* @param mixed[] $values
|
||||
*/
|
||||
public static function everyIs(iterable $values, string $expected): bool
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
if (!static::is($value, $expected)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is an integer or a float.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumber($value): bool
|
||||
{
|
||||
return is_int($value) || is_float($value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is an integer.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumericInt($value): bool
|
||||
{
|
||||
return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a floating point number in decimal base.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumeric($value): bool
|
||||
{
|
||||
return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]*[.]?[0-9]+$#D', $value));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is a syntactically correct callback.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isCallable($value): bool
|
||||
{
|
||||
return $value && is_callable($value, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is an UTF-8 encoded string.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isUnicode($value): bool
|
||||
{
|
||||
return is_string($value) && preg_match('##u', $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is "falsy".
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNone($value): bool
|
||||
{
|
||||
return $value == null; // intentionally ==
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function isMixed(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a variable is a zero-based integer indexed array.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isList($value): bool
|
||||
{
|
||||
return Arrays::isList($value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is a value in specified min and max value pair?
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isInRange($value, array $range): bool
|
||||
{
|
||||
if ($value === null || !(isset($range[0]) || isset($range[1]))) {
|
||||
return false;
|
||||
}
|
||||
$limit = $range[0] ?? $range[1];
|
||||
if (is_string($limit)) {
|
||||
$value = (string) $value;
|
||||
} elseif ($limit instanceof \DateTimeInterface) {
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
return false;
|
||||
}
|
||||
} elseif (is_numeric($value)) {
|
||||
$value *= 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a valid email address.
|
||||
*/
|
||||
public static function isEmail(string $value): bool
|
||||
{
|
||||
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
|
||||
$alpha = "a-z\x80-\xFF"; // superset of IDN
|
||||
return (bool) preg_match(<<<XX
|
||||
(^
|
||||
("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*) # quoted or unquoted
|
||||
@
|
||||
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
|
||||
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|
||||
$)Dix
|
||||
XX
|
||||
, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a valid http(s) URL.
|
||||
*/
|
||||
public static function isUrl(string $value): bool
|
||||
{
|
||||
$alpha = "a-z\x80-\xFF";
|
||||
return (bool) preg_match(<<<XX
|
||||
(^
|
||||
https?://(
|
||||
(([-_0-9$alpha]+\\.)* # subdomain
|
||||
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
|
||||
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|
||||
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
|
||||
|\[[0-9a-f:]{3,39}\] # IPv6
|
||||
)(:\\d{1,5})? # port
|
||||
(/\\S*)? # path
|
||||
(\?\\S*)? # query
|
||||
(\#\\S*)? # fragment
|
||||
$)Dix
|
||||
XX
|
||||
, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a valid URI according to RFC 1738.
|
||||
*/
|
||||
public static function isUri(string $value): bool
|
||||
{
|
||||
return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the input is a class, interface or trait.
|
||||
*/
|
||||
public static function isType(string $type): bool
|
||||
{
|
||||
return class_exists($type) || interface_exists($type) || trait_exists($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the input is a valid PHP identifier.
|
||||
*/
|
||||
public static function isPhpIdentifier(string $value): bool
|
||||
{
|
||||
return is_string($value) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value);
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when the value of an argument is
|
||||
* outside the allowable range of values as defined by the invoked method.
|
||||
*/
|
||||
class ArgumentOutOfRangeException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a method call is invalid for the object's
|
||||
* current state, method has been invoked at an illegal or inappropriate time.
|
||||
*/
|
||||
class InvalidStateException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is not implemented.
|
||||
*/
|
||||
class NotImplementedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an invoked method is not supported. For scenarios where
|
||||
* it is sometimes possible to perform the requested operation, see InvalidStateException.
|
||||
*/
|
||||
class NotSupportedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is deprecated.
|
||||
*/
|
||||
class DeprecatedException extends NotSupportedException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a class member (property or method) fails.
|
||||
*/
|
||||
class MemberAccessException extends \Error
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an I/O error occurs.
|
||||
*/
|
||||
class IOException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a file that does not exist on disk.
|
||||
*/
|
||||
class FileNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when part of a file or directory cannot be found.
|
||||
*/
|
||||
class DirectoryNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an argument does not match with the expected value.
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an illegal index was requested.
|
||||
*/
|
||||
class OutOfRangeException extends \OutOfRangeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a value (typically returned by function) does not match with the expected value.
|
||||
*/
|
||||
class UnexpectedValueException extends \UnexpectedValueException
|
||||
{
|
||||
}
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an image error occurs.
|
||||
*/
|
||||
class ImageException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates invalid image file.
|
||||
*/
|
||||
class UnknownImageFileException extends ImageException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates error of JSON encoding/decoding.
|
||||
*/
|
||||
class JsonException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates error of the last Regexp execution.
|
||||
*/
|
||||
class RegexpException extends \Exception
|
||||
{
|
||||
public const MESSAGES = [
|
||||
PREG_INTERNAL_ERROR => 'Internal error',
|
||||
PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
|
||||
PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
|
||||
PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
|
||||
PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
|
||||
6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates assertion error.
|
||||
*/
|
||||
class AssertionException extends \Exception
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user