This commit is contained in:
2022-07-18 08:00:16 +03:00
parent 42dd4c60f2
commit 42a8133706
2387 changed files with 210708 additions and 29 deletions

19
vendor/nette/utils/.phpstorm.meta.php vendored Normal file
View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace PHPSTORM_META;
override(\Nette\Utils\Arrays::get(0), elementType(0));
override(\Nette\Utils\Arrays::getRef(0), elementType(0));
override(\Nette\Utils\Arrays::grep(0), type(0));
override(\Nette\Utils\Arrays::toObject(0), type(1));
expectedArguments(\Nette\Utils\Arrays::grep(), 2, PREG_GREP_INVERT);
expectedArguments(\Nette\Utils\Image::resize(), 2, \Nette\Utils\Image::SHRINK_ONLY, \Nette\Utils\Image::STRETCH, \Nette\Utils\Image::FIT, \Nette\Utils\Image::FILL, \Nette\Utils\Image::EXACT);
expectedArguments(\Nette\Utils\Image::calculateSize(), 4, \Nette\Utils\Image::SHRINK_ONLY, \Nette\Utils\Image::STRETCH, \Nette\Utils\Image::FIT, \Nette\Utils\Image::FILL, \Nette\Utils\Image::EXACT);
expectedArguments(\Nette\Utils\Json::encode(), 1, \Nette\Utils\Json::PRETTY);
expectedArguments(\Nette\Utils\Json::decode(), 1, \Nette\Utils\Json::FORCE_ARRAY);
expectedArguments(\Nette\Utils\Strings::split(), 2, \PREG_SPLIT_NO_EMPTY | \PREG_OFFSET_CAPTURE);
expectedArguments(\Nette\Utils\Strings::match(), 2, \PREG_OFFSET_CAPTURE | \PREG_UNMATCHED_AS_NULL);
expectedArguments(\Nette\Utils\Strings::matchAll(), 2, \PREG_OFFSET_CAPTURE | \PREG_UNMATCHED_AS_NULL | \PREG_PATTERN_ORDER);

50
vendor/nette/utils/composer.json vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "nette/utils",
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
"keywords": ["nette", "images", "json", "password", "validation", "utility", "string", "array", "core", "slugify", "utf-8", "unicode", "paginator", "datetime"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.2 <8.2"
},
"require-dev": {
"nette/tester": "~2.0",
"tracy/tracy": "^2.3",
"phpstan/phpstan": "^1.0"
},
"conflict": {
"nette/di": "<3.0.6"
},
"suggest": {
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-xml": "to use Strings::length() etc. when mbstring is not available",
"ext-gd": "to use Image",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests -s"
},
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
}
}

33
vendor/nette/utils/contributing.md vendored Normal file
View File

@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:

60
vendor/nette/utils/license.md vendored Normal file
View File

@@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)

14
vendor/nette/utils/ncs.php vendored Normal file
View File

@@ -0,0 +1,14 @@
<?php
/**
* Rules for Nette Coding Standard
* https://github.com/nette/coding-standard
*/
declare(strict_types=1);
return [
// use function in Arrays.php, Callback.php, Html.php, Strings.php
'single_import_per_statement' => false,
'ordered_imports' => false,
];

13
vendor/nette/utils/ncs.xml vendored Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<ruleset name="Custom" namespace="Nette">
<rule ref="$presets/php72.xml"/>
<!-- bug in SlevomatCodingStandard -->
<rule ref="SlevomatCodingStandard.Operators.RequireCombinedAssignmentOperator">
<severity>0</severity>
</rule>
<!-- bug in FunctionSpacingSniff -->
<exclude-pattern>./tests/Utils/Reflection.getDeclaringMethod.alias.phpt</exclude-pattern>
<exclude-pattern>./tests/Utils/Reflection.getDeclaringMethod.insteadof.phpt</exclude-pattern>
</ruleset>

54
vendor/nette/utils/readme.md vendored Normal file
View File

@@ -0,0 +1,54 @@
Nette Utility Classes
=====================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils)
[![Tests](https://github.com/nette/utils/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/utils/actions)
[![Coverage Status](https://coveralls.io/repos/github/nette/utils/badge.svg?branch=master)](https://coveralls.io/github/nette/utils?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/utils/v/stable)](https://github.com/nette/utils/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/utils/blob/master/license.md)
Introduction
------------
In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use:
- [Arrays](https://doc.nette.org/arrays) - manipulate arrays
- [Callback](https://doc.nette.org/callback) - PHP callbacks
- [Date and Time](https://doc.nette.org/datetime) - modify times and dates
- [Filesystem](https://doc.nette.org/filesystem) - copying, renaming, …
- [Helper Functions](https://doc.nette.org/helpers)
- [HTML elements](https://doc.nette.org/html-elements) - generate HTML
- [Images](https://doc.nette.org/images) - crop, resize, rotate images
- [JSON](https://doc.nette.org/json) - encoding and decoding
- [Generating Random Strings](https://doc.nette.org/random)
- [Paginator](https://doc.nette.org/paginator) - pagination math
- [PHP Reflection](https://doc.nette.org/reflection)
- [Strings](https://doc.nette.org/strings) - useful text functions
- [SmartObject](https://doc.nette.org/smartobject) - PHP object enhancements
- [Validation](https://doc.nette.org/validators) - validate inputs
- [Type](https://doc.nette.org/type) - PHP data type
Installation
------------
The recommended way to install is via Composer:
```
composer require nette/utils
```
- Nette Utils 3.2 is compatible with PHP 7.2 to 8.1
- Nette Utils 3.1 is compatible with PHP 7.1 to 8.0
- Nette Utils 3.0 is compatible with PHP 7.1 to 8.0
- Nette Utils 2.5 is compatible with PHP 5.6 to 8.0
[Support Me](https://github.com/sponsors/dg)
--------------------------------------------
Do you like Nette Utils? Are you looking forward to the new features?
[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
Thank you!

View File

@@ -0,0 +1,22 @@
<?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;
interface HtmlStringable
{
/**
* Returns string in HTML format
*/
function __toString(): string;
}
interface_exists(Utils\IHtmlString::class);

View File

@@ -0,0 +1,167 @@
<?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.', self::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();
}
}

View File

@@ -0,0 +1,35 @@
<?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;
}
#[\ReturnTypeWillChange]
public function current()
{
return ($this->callback)(parent::current(), parent::key());
}
}

140
vendor/nette/utils/src/SmartObject.php vendored Normal file
View File

@@ -0,0 +1,140 @@
<?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
{
/**
* @throws MemberAccessException
*/
public function __call(string $name, array $args)
{
$class = static::class;
if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers
$handlers = $this->$name ?? null;
if (is_iterable($handlers)) {
foreach ($handlers as $handler) {
$handler(...$args);
}
} elseif ($handlers !== null) {
throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.');
}
} else {
ObjectHelpers::strictCall($class, $name);
}
}
/**
* @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 = static::class;
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') . ucfirst($name);
if ($prop & 0b10000) {
$trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
$loc = isset($trace['file'], $trace['line'])
? " in $trace[file] on line $trace[line]"
: '';
trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
}
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 = static::class;
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.");
}
$m = 'set' . ucfirst($name);
if ($prop & 0b10000) {
$trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
$loc = isset($trace['file'], $trace['line'])
? " in $trace[file] on line $trace[line]"
: '';
trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
}
$this->$m($value);
} else {
ObjectHelpers::strictSet($class, $name);
}
}
/**
* @return void
* @throws MemberAccessException
*/
public function __unset(string $name)
{
$class = static::class;
if (!ObjectHelpers::hasProperty($class, $name)) {
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
}
}
public function __isset(string $name): bool
{
return isset(ObjectHelpers::getMagicProperties(static::class)[$name]);
}
}

37
vendor/nette/utils/src/StaticClass.php vendored Normal file
View File

@@ -0,0 +1,37 @@
<?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
{
/**
* @return never
* @throws \Error
*/
final public function __construct()
{
throw new \Error('Class ' . static::class . ' 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);
}
}

27
vendor/nette/utils/src/Translator.php vendored Normal file
View File

@@ -0,0 +1,27 @@
<?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 Translator
{
/**
* Translates the given string.
* @param mixed $message
* @param mixed ...$parameters
*/
function translate($message, ...$parameters): string;
}
interface_exists(ITranslator::class);

View File

@@ -0,0 +1,103 @@
<?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.
* @template T
*/
class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
{
/**
* Transforms array to ArrayHash.
* @param array<T> $array
* @return static
*/
public static function from(array $array, bool $recursive = true)
{
$obj = new static;
foreach ($array as $key => $value) {
$obj->$key = $recursive && is_array($value)
? static::from($value, true)
: $value;
}
return $obj;
}
/**
* Returns an iterator over all items.
* @return \RecursiveArrayIterator<array-key, T>
*/
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 T $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 T
*/
#[\ReturnTypeWillChange]
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);
}
}

View File

@@ -0,0 +1,135 @@
<?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).
* @template T
*/
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
{
use Nette\SmartObject;
/** @var mixed[] */
private $list = [];
/**
* Transforms array to ArrayList.
* @param array<T> $array
* @return static
*/
public static function from(array $array)
{
if (!Arrays::isList($array)) {
throw new Nette\InvalidArgumentException('Array is not valid list.');
}
$obj = new static;
$obj->list = $array;
return $obj;
}
/**
* Returns an iterator over all items.
* @return \ArrayIterator<int, T>
*/
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 T $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 T
* @throws Nette\OutOfRangeException
*/
#[\ReturnTypeWillChange]
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 T $value
*/
public function prepend($value): void
{
$first = array_slice($this->list, 0, 1);
$this->offsetSet(0, $value);
array_splice($this->list, 1, 0, $first);
}
}

454
vendor/nette/utils/src/Utils/Arrays.php vendored Normal file
View File

@@ -0,0 +1,454 @@
<?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. If it does not exist, it throws an exception, unless a default value is set.
* @template T
* @param array<T> $array
* @param array-key|array-key[] $key
* @param ?T $default
* @return ?T
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
*/
public static function get(array $array, $key, $default = null)
{
foreach (is_array($key) ? $key : [$key] as $k) {
if (is_array($array) && array_key_exists($k, $array)) {
$array = $array[$k];
} else {
if (func_num_args() < 3) {
throw new Nette\InvalidArgumentException("Missing item '$k'.");
}
return $default;
}
}
return $array;
}
/**
* Returns reference to array item. If the index does not exist, new one is created with value null.
* @template T
* @param array<T> $array
* @param array-key|array-key[] $key
* @return ?T
* @throws Nette\InvalidArgumentException if traversed item is not an array
*/
public static function &getRef(array &$array, $key)
{
foreach (is_array($key) ? $key : [$key] as $k) {
if (is_array($array) || $array === null) {
$array = &$array[$k];
} else {
throw new Nette\InvalidArgumentException('Traversed item is not an array.');
}
}
return $array;
}
/**
* Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
* the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
* the value from the first array in the case of a key collision.
* @template T1
* @template T2
* @param array<T1> $array1
* @param array<T2> $array2
* @return array<T1|T2>
*/
public static function mergeTree(array $array1, array $array2): array
{
$res = $array1 + $array2;
foreach (array_intersect_key($array1, $array2) as $k => $v) {
if (is_array($v) && is_array($array2[$k])) {
$res[$k] = self::mergeTree($v, $array2[$k]);
}
}
return $res;
}
/**
* Returns zero-indexed position of given array key. Returns null if key is not found.
* @param array-key $key
* @return int|null offset if it is found, null otherwise
*/
public static function getKeyOffset(array $array, $key): ?int
{
return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true));
}
/**
* @deprecated use getKeyOffset()
*/
public static function searchKey(array $array, $key): ?int
{
return self::getKeyOffset($array, $key);
}
/**
* Tests an array for the presence of value.
* @param mixed $value
*/
public static function contains(array $array, $value): bool
{
return in_array($value, $array, true);
}
/**
* Returns the first item from the array or null if array is empty.
* @template T
* @param array<T> $array
* @return ?T
*/
public static function first(array $array)
{
return count($array) ? reset($array) : null;
}
/**
* Returns the last item from the array or null if array is empty.
* @template T
* @param array<T> $array
* @return ?T
*/
public static function last(array $array)
{
return count($array) ? end($array) : null;
}
/**
* Inserts the contents of the $inserted array into the $array immediately after the $key.
* If $key is null (or does not exist), it is inserted at the beginning.
* @param array-key|null $key
*/
public static function insertBefore(array &$array, $key, array $inserted): void
{
$offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
$array = array_slice($array, 0, $offset, true)
+ $inserted
+ array_slice($array, $offset, count($array), true);
}
/**
* Inserts the contents of the $inserted array into the $array before the $key.
* If $key is null (or does not exist), it is inserted at the end.
* @param array-key|null $key
*/
public static function insertAfter(array &$array, $key, array $inserted): void
{
if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
$offset = count($array) - 1;
}
$array = array_slice($array, 0, $offset + 1, true)
+ $inserted
+ array_slice($array, $offset + 1, count($array), true);
}
/**
* Renames key in array.
* @param array-key $oldKey
* @param array-key $newKey
*/
public static function renameKey(array &$array, $oldKey, $newKey): bool
{
$offset = self::getKeyOffset($array, $oldKey);
if ($offset === null) {
return false;
}
$val = &$array[$oldKey];
$keys = array_keys($array);
$keys[$offset] = $newKey;
$array = array_combine($keys, $array);
$array[$newKey] = &$val;
return true;
}
/**
* Returns only those array items, which matches a regular expression $pattern.
* @param string[] $array
* @return string[]
*/
public static function grep(array $array, string $pattern, int $flags = 0): array
{
return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
}
/**
* Transforms multidimensional array to flat array.
*/
public static function flatten(array $array, 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($array, $cb);
return $res;
}
/**
* Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
* @param mixed $value
*/
public static function isList($value): bool
{
return is_array($value) && (PHP_VERSION_ID < 80100
? !$value || array_keys($value) === range(0, count($value) - 1)
: array_is_list($value)
);
}
/**
* 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 $array, $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 ($array 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 array to associative array. Replace numeric keys with their values, the new value will be $filling.
* @param mixed $filling
*/
public static function normalize(array $array, $filling = null): array
{
$res = [];
foreach ($array as $k => $v) {
$res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
}
return $res;
}
/**
* Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
* or returns $default, if provided.
* @template T
* @param array<T> $array
* @param array-key $key
* @param ?T $default
* @return ?T
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
*/
public static function pick(array &$array, $key, $default = null)
{
if (array_key_exists($key, $array)) {
$value = $array[$key];
unset($array[$key]);
return $value;
} elseif (func_num_args() < 3) {
throw new Nette\InvalidArgumentException("Missing item '$key'.");
} else {
return $default;
}
}
/**
* Tests whether at least one element in the array passes the test implemented by the
* provided callback with signature `function ($value, $key, array $array): bool`.
*/
public static function some(iterable $array, callable $callback): bool
{
foreach ($array as $k => $v) {
if ($callback($v, $k, $array)) {
return true;
}
}
return false;
}
/**
* Tests whether all elements in the array pass the test implemented by the provided function,
* which has the signature `function ($value, $key, array $array): bool`.
*/
public static function every(iterable $array, callable $callback): bool
{
foreach ($array as $k => $v) {
if (!$callback($v, $k, $array)) {
return false;
}
}
return true;
}
/**
* Calls $callback on all elements in the array and returns the array of return values.
* The callback has the signature `function ($value, $key, array $array): bool`.
*/
public static function map(iterable $array, callable $callback): array
{
$res = [];
foreach ($array as $k => $v) {
$res[$k] = $callback($v, $k, $array);
}
return $res;
}
/**
* Invokes all callbacks and returns array of results.
* @param callable[] $callbacks
*/
public static function invoke(iterable $callbacks, ...$args): array
{
$res = [];
foreach ($callbacks as $k => $cb) {
$res[$k] = $cb(...$args);
}
return $res;
}
/**
* Invokes method on every object in an array and returns array of results.
* @param object[] $objects
*/
public static function invokeMethod(iterable $objects, string $method, ...$args): array
{
$res = [];
foreach ($objects as $k => $obj) {
$res[$k] = $obj->$method(...$args);
}
return $res;
}
/**
* Copies the elements of the $array array to the $object object and then returns it.
* @template T of object
* @param T $object
* @return T
*/
public static function toObject(iterable $array, $object)
{
foreach ($array as $k => $v) {
$object->$k = $v;
}
return $object;
}
/**
* Converts value to array key.
* @param mixed $value
* @return array-key
*/
public static function toKey($value)
{
return key([$value => null]);
}
/**
* Returns copy of the $array where every item is converted to string
* and prefixed by $prefix and suffixed by $suffix.
* @param string[] $array
* @return string[]
*/
public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
{
$res = [];
foreach ($array as $k => $v) {
$res[$k] = $prefix . $v . $suffix;
}
return $res;
}
}

View File

@@ -0,0 +1,184 @@
<?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
{
trigger_error(__METHOD__ . '() is deprecated, use Closure::fromCallable().', E_USER_DEPRECATED);
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();
}
}
/**
* Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
* that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
* @param mixed $callable
* @return callable
* @throws Nette\InvalidArgumentException
*/
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;
}
/**
* Converts PHP callback to textual form. Class or method may not exists.
* @param mixed $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;
}
}
/**
* Returns reflection for method or function used in PHP callback.
* @param callable $callable type check is escalated to ReflectionException
* @return \ReflectionMethod|\ReflectionFunction
* @throws \ReflectionException if callback is not valid
*/
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);
}
}
/**
* Checks whether PHP callback is function or static method.
*/
public static function isStatic(callable $callable): bool
{
return is_array($callable) ? is_string($callable[0]) : is_string($callable);
}
/**
* Unwraps closure created by Closure::fromCallable().
* @return callable|array
*/
public static function unwrap(\Closure $closure)
{
$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;
}
}
}

View File

@@ -0,0 +1,147 @@
<?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;
/**
* Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object.
* @param string|int|\DateTimeInterface $time
* @return static
* @throws \Exception if the date and time are not valid.
*/
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
* @throws Nette\InvalidArgumentException if the date and time are not valid.
*/
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
*/
#[\ReturnTypeWillChange]
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');
}
/**
* Returns the date and time in the format 'Y-m-d H:i:s'.
*/
public function __toString(): string
{
return $this->format('Y-m-d H:i:s');
}
/**
* Creates a copy with a modified time.
* @return static
*/
public function modifyClone(string $modify = '')
{
$dolly = clone $this;
return $modify ? $dolly->modify($modify) : $dolly;
}
}

View File

@@ -0,0 +1,261 @@
<?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 if it doesn't exist.
* @throws Nette\IOException on error occurred
*/
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(sprintf(
"Unable to create directory '%s' with mode %s. %s",
self::normalizePath($dir),
decoct($mode),
Helpers::getLastError()
));
}
}
/**
* Copies a file or a directory. Overwrites existing files and directories by default.
* @throws Nette\IOException on error occurred
* @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
*/
public static function copy(string $origin, string $target, bool $overwrite = true): void
{
if (stream_is_local($origin) && !file_exists($origin)) {
throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
} elseif (!$overwrite && file_exists($target)) {
throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
} elseif (is_dir($origin)) {
static::createDir($target);
foreach (new \FilesystemIterator($target) as $item) {
static::delete($item->getPathname());
}
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
if ($item->isDir()) {
static::createDir($target . '/' . $iterator->getSubPathName());
} else {
static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
}
}
} else {
static::createDir(dirname($target));
if (
($s = @fopen($origin, 'rb'))
&& ($d = @fopen($target, 'wb'))
&& @stream_copy_to_stream($s, $d) === false
) { // @ is escalated to exception
throw new Nette\IOException(sprintf(
"Unable to copy file '%s' to '%s'. %s",
self::normalizePath($origin),
self::normalizePath($target),
Helpers::getLastError()
));
}
}
}
/**
* Deletes a file or directory if exists.
* @throws Nette\IOException on error occurred
*/
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(sprintf(
"Unable to delete '%s'. %s",
self::normalizePath($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(sprintf(
"Unable to delete directory '%s'. %s",
self::normalizePath($path),
Helpers::getLastError()
));
}
}
}
/**
* Renames or moves a file or a directory. Overwrites existing files and directories by default.
* @throws Nette\IOException on error occurred
* @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
*/
public static function rename(string $origin, string $target, bool $overwrite = true): void
{
if (!$overwrite && file_exists($target)) {
throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
} elseif (!file_exists($origin)) {
throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
} else {
static::createDir(dirname($target));
if (realpath($origin) !== realpath($target)) {
static::delete($target);
}
if (!@rename($origin, $target)) { // @ is escalated to exception
throw new Nette\IOException(sprintf(
"Unable to rename file or directory '%s' to '%s'. %s",
self::normalizePath($origin),
self::normalizePath($target),
Helpers::getLastError()
));
}
}
}
/**
* Reads the content of a file.
* @throws Nette\IOException on error occurred
*/
public static function read(string $file): string
{
$content = @file_get_contents($file); // @ is escalated to exception
if ($content === false) {
throw new Nette\IOException(sprintf(
"Unable to read file '%s'. %s",
self::normalizePath($file),
Helpers::getLastError()
));
}
return $content;
}
/**
* Writes the string to a file.
* @throws Nette\IOException on error occurred
*/
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(sprintf(
"Unable to write file '%s'. %s",
self::normalizePath($file),
Helpers::getLastError()
));
}
if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
throw new Nette\IOException(sprintf(
"Unable to chmod file '%s' to mode %s. %s",
self::normalizePath($file),
decoct($mode),
Helpers::getLastError()
));
}
}
/**
* Fixes permissions to a specific file or directory. Directories can be fixed recursively.
* @throws Nette\IOException on error occurred
*/
public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
{
if (is_file($path)) {
if (!@chmod($path, $fileMode)) { // @ is escalated to exception
throw new Nette\IOException(sprintf(
"Unable to chmod file '%s' to mode %s. %s",
self::normalizePath($path),
decoct($fileMode),
Helpers::getLastError()
));
}
} elseif (is_dir($path)) {
foreach (new \FilesystemIterator($path) as $item) {
static::makeWritable($item->getPathname(), $dirMode, $fileMode);
}
if (!@chmod($path, $dirMode)) { // @ is escalated to exception
throw new Nette\IOException(sprintf(
"Unable to chmod directory '%s' to mode %s. %s",
self::normalizePath($path),
decoct($dirMode),
Helpers::getLastError()
));
}
} else {
throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path)));
}
}
/**
* Determines if the path is absolute.
*/
public static function isAbsolute(string $path): bool
{
return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
}
/**
* Normalizes `..` and `.` 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 segments of the path and normalizes the result.
*/
public static function joinPaths(string ...$paths): string
{
return self::normalizePath(implode('/', $paths));
}
}

107
vendor/nette/utils/src/Utils/Floats.php vendored Normal file
View File

@@ -0,0 +1,107 @@
<?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;
/**
* Floating-point numbers comparison.
*/
class Floats
{
use Nette\StaticClass;
private const EPSILON = 1e-10;
public static function isZero(float $value): bool
{
return abs($value) < self::EPSILON;
}
public static function isInteger(float $value): bool
{
return abs(round($value) - $value) < self::EPSILON;
}
/**
* Compare two floats. If $a < $b it returns -1, if they are equal it returns 0 and if $a > $b it returns 1
* @throws \LogicException if one of parameters is NAN
*/
public static function compare(float $a, float $b): int
{
if (is_nan($a) || is_nan($b)) {
throw new \LogicException('Trying to compare NAN');
} elseif (!is_finite($a) && !is_finite($b) && $a === $b) {
return 0;
}
$diff = abs($a - $b);
if (($diff < self::EPSILON || ($diff / max(abs($a), abs($b)) < self::EPSILON))) {
return 0;
}
return $a < $b ? -1 : 1;
}
/**
* Returns true if $a = $b
* @throws \LogicException if one of parameters is NAN
*/
public static function areEqual(float $a, float $b): bool
{
return self::compare($a, $b) === 0;
}
/**
* Returns true if $a < $b
* @throws \LogicException if one of parameters is NAN
*/
public static function isLessThan(float $a, float $b): bool
{
return self::compare($a, $b) < 0;
}
/**
* Returns true if $a <= $b
* @throws \LogicException if one of parameters is NAN
*/
public static function isLessThanOrEqualTo(float $a, float $b): bool
{
return self::compare($a, $b) <= 0;
}
/**
* Returns true if $a > $b
* @throws \LogicException if one of parameters is NAN
*/
public static function isGreaterThan(float $a, float $b): bool
{
return self::compare($a, $b) > 0;
}
/**
* Returns true if $a >= $b
* @throws \LogicException if one of parameters is NAN
*/
public static function isGreaterThanOrEqualTo(float $a, float $b): bool
{
return self::compare($a, $b) >= 0;
}
}

View File

@@ -0,0 +1,91 @@
<?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;
class Helpers
{
/**
* Executes a callback and returns the captured output as 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 occurred PHP error or an empty string if no error occurred. Unlike error_get_last(),
* it is nit affected by the PHP directive html_errors and always returns text, not HTML.
*/
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, does not change other values.
* @param mixed $value
* @return mixed
*/
public static function falseToNull($value)
{
return $value === false ? null : $value;
}
/**
* Returns value clamped to the inclusive range of min and max.
* @param int|float $value
* @param int|float $min
* @param int|float $max
* @return int|float
*/
public static function clamp($value, $min, $max)
{
if ($min > $max) {
throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max).");
}
return min(max($value, $min), $max);
}
/**
* Looks for a string from possibilities that is most similar to value, but not the same (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;
}
}

887
vendor/nette/utils/src/Utils/Html.php vendored Normal file
View File

@@ -0,0 +1,887 @@
<?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\HtmlStringable;
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, HtmlStringable
{
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, HtmlStringable|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 HtmlStringable|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 HtmlStringable|string|int|float $text
* @return static
*/
final public function setText($text)
{
if (!$text instanceof HtmlStringable) {
$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 HtmlStringable|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 HtmlStringable|string|int|float $text
* @return static
*/
public function addText($text)
{
if (!$text instanceof HtmlStringable) {
$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 HtmlStringable|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 HtmlStringable|string
*/
#[\ReturnTypeWillChange]
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.
* @return \ArrayIterator<int, HtmlStringable|string>
*/
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, '<'],
['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'],
$value
)
. (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
. $q;
}
$s = str_replace('@', '&#64;', $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;
}
}
}
}

761
vendor/nette/utils/src/Utils/Image.php vendored Normal file
View File

@@ -0,0 +1,761 @@
<?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. Supported types are JPEG, PNG, GIF, WEBP, AVIF and BMP.
*
* <code>
* $image = Image::fromFile('nette.jpg');
* $image->resize(150, 100);
* $image->sharpen();
* $image->send();
* </code>
*
* @method Image affine(array $affine, array $clip = null)
* @method array affineMatrixConcat(array $m1, array $m2)
* @method array affineMatrixGet(int $type, mixed $options = null)
* @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 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 setInterpolation(int $method = IMG_BILINEAR_FIXED)
* @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|\GdImage $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,
AVIF = 19, // IMAGETYPE_AVIF,
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::AVIF => 'avif', self::BMP => 'bmp'];
/** @var resource|\GdImage */
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)),
];
}
/**
* Reads an image from a file and returns its type in $type.
* @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 &$type = null)
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
}
$type = self::detectTypeFromFile($file);
if (!$type) {
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
}
return self::invokeSafe('imagecreatefrom' . self::FORMATS[$type], $file, "Unable to open file '$file'.", __METHOD__);
}
/**
* Reads an image from a string and returns its type in $type.
* @return static
* @throws Nette\NotSupportedException if gd extension is not loaded
* @throws ImageException
*/
public static function fromString(string $s, ?int &$type = null)
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
}
$type = self::detectTypeFromString($s);
if (!$type) {
throw new UnknownImageFileException('Unknown type of image.');
}
return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
}
private static function invokeSafe(string $func, string $arg, string $message, string $callee): self
{
$errors = [];
$res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void {
$errors[] = $message;
});
if (!$res) {
throw new ImageException($message . ' Errors: ' . implode(', ', $errors));
} elseif ($errors) {
trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING);
}
return new static($res);
}
/**
* Creates a new true color image of the given dimensions. The default color is black.
* @return static
* @throws Nette\NotSupportedException if gd extension is not loaded
*/
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);
}
/**
* Returns the type of image from file.
*/
public static function detectTypeFromFile(string $file): ?int
{
$type = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error
return isset(self::FORMATS[$type]) ? $type : null;
}
/**
* Returns the type of image from string.
*/
public static function detectTypeFromString(string $s): ?int
{
$type = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error
return isset(self::FORMATS[$type]) ? $type : null;
}
/**
* Returns the file extension for the given `Image::XXX` constant.
*/
public static function typeToExtension(int $type): string
{
if (!isset(self::FORMATS[$type])) {
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
}
return self::FORMATS[$type];
}
/**
* Returns the mime type for the given `Image::XXX` constant.
*/
public static function typeToMimeType(int $type): string
{
return 'image/' . self::typeToExtension($type);
}
/**
* Wraps GD image.
* @param resource|\GdImage $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|\GdImage $image
* @return static
*/
protected function setImageResource($image)
{
if (!$image instanceof \GdImage && !(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|\GdImage
*/
public function getImageResource()
{
return $this->image;
}
/**
* Scales an 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) {
} elseif (self::isPercent($newWidth)) {
$newWidth = (int) round($srcWidth / 100 * abs($newWidth));
$percents = true;
} else {
$newWidth = abs($newWidth);
}
if ($newHeight === null) {
} elseif (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];
}
/**
* Sharpens image a little bit.
* @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 in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
* @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 in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
*/
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 in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
* @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::AVIF:
$quality = $quality === null ? 30 : max(0, min(100, $quality));
$success = @imageavif($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(static::class, $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 $res instanceof \GdImage || (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
vendor/nette/utils/src/Utils/Json.php vendored Normal file
View File

@@ -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 = JSON_OBJECT_AS_ARRAY;
public const PRETTY = JSON_PRETTY_PRINT;
public const ESCAPE_UNICODE = 1 << 19;
/**
* Converts value to JSON format. The flag can be Json::PRETTY, which formats JSON for easier reading and clarity,
* and Json::ESCAPE_UNICODE for ASCII output.
* @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::ESCAPE_UNICODE)
| (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;
}
/**
* Parses JSON to PHP value. The flag can be Json::FORCE_ARRAY, which forces an array instead of an object as the return value.
* @return mixed
* @throws JsonException
*/
public static function decode(string $json, int $flags = 0)
{
$value = json_decode($json, null, 512, $flags | JSON_BIGINT_AS_STRING);
if ($error = json_last_error()) {
throw new JsonException(json_last_error_msg(), $error);
}
return $value;
}
}

View File

@@ -0,0 +1,228 @@
<?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;
/**
* @return never
* @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?" : '.'));
}
/**
* @return never
* @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?" : '.'));
}
/**
* @return never
* @throws MemberAccessException
*/
public static function strictCall(string $class, string $method, array $additionalMethods = []): void
{
$trace = debug_backtrace(0, 3); // suppose this method is called from __call()
$context = ($trace[1]['function'] ?? null) === '__call'
? ($trace[2]['class'] ?? null)
: null;
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
$class = get_parent_class($context);
}
if (method_exists($class, $method)) { // insufficient visibility
$rm = new \ReflectionMethod($class, $method);
$visibility = $rm->isPrivate()
? 'private '
: ($rm->isProtected() ? 'protected ' : '');
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
} else {
$hint = self::getSuggestion(array_merge(
get_class_methods($class),
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
$additionalMethods
), $method);
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
}
}
/**
* @return never
* @throws MemberAccessException
*/
public static function strictStaticCall(string $class, string $method): void
{
$trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic()
$context = ($trace[1]['function'] ?? null) === '__callStatic'
? ($trace[2]['class'] ?? null)
: null;
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
$class = get_parent_class($context);
}
if (method_exists($class, $method)) { // insufficient visibility
$rm = new \ReflectionMethod($class, $method);
$visibility = $rm->isPrivate()
? 'private '
: ($rm->isProtected() ? 'protected ' : '');
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
} else {
$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|-deprecated) [ \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 | ($type === '-deprecated') << 4;
}
}
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;
}
}

View File

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

View File

@@ -0,0 +1,242 @@
<?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-read int $firstItemOnPage
* @property-read int $lastItemOnPage
* @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);
}
/**
* Returns the sequence number of the first element on the page
*/
public function getFirstItemOnPage(): int
{
return $this->itemCount !== 0
? $this->offset + 1
: 0;
}
/**
* Returns the sequence number of the last element on the page
*/
public function getLastItemOnPage(): int
{
return $this->offset + $this->length;
}
/**
* 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);
}
}

46
vendor/nette/utils/src/Utils/Random.php vendored Normal file
View File

@@ -0,0 +1,46 @@
<?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;
/**
* Generates a random string of given length from characters specified in second argument.
* Supports intervals, such as `0-9` or `A-Z`.
*/
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;
}
}

View File

@@ -0,0 +1,425 @@
<?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, 'mixed' => 1, 'false' => 1,
'never' => 1,
];
private const CLASS_KEYWORDS = [
'self' => 1, 'parent' => 1, 'static' => 1,
];
/**
* Determines if type is PHP built-in type. Otherwise, it is the class name.
*/
public static function isBuiltinType(string $type): bool
{
return isset(self::BUILTIN_TYPES[strtolower($type)]);
}
/**
* Determines if type is special class name self/parent/static.
*/
public static function isClassKeyword(string $name): bool
{
return isset(self::CLASS_KEYWORDS[strtolower($name)]);
}
/**
* Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
* If the function does not have a return type, it returns null.
* If the function has union or intersection type, it throws Nette\InvalidStateException.
*/
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
{
$type = $func->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod ? $func->getTentativeReturnType() : null);
return self::getType($func, $type);
}
/**
* @deprecated
*/
public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
{
$type = Type::fromReflection($func);
return $type ? $type->getNames() : [];
}
/**
* Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
* If the parameter does not have a type, it returns null.
* If the parameter has union or intersection type, it throws Nette\InvalidStateException.
*/
public static function getParameterType(\ReflectionParameter $param): ?string
{
return self::getType($param, $param->getType());
}
/**
* @deprecated
*/
public static function getParameterTypes(\ReflectionParameter $param): array
{
$type = Type::fromReflection($param);
return $type ? $type->getNames() : [];
}
/**
* Returns the type of given property and normalizes `self` and `parent` to the actual class names.
* If the property does not have a type, it returns null.
* If the property has union or intersection type, it throws Nette\InvalidStateException.
*/
public static function getPropertyType(\ReflectionProperty $prop): ?string
{
return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null);
}
/**
* @deprecated
*/
public static function getPropertyTypes(\ReflectionProperty $prop): array
{
$type = Type::fromReflection($prop);
return $type ? $type->getNames() : [];
}
/**
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
*/
private static function getType($reflection, ?\ReflectionType $type): ?string
{
if ($type === null) {
return null;
} elseif ($type instanceof \ReflectionNamedType) {
return Type::resolve($type->getName(), $reflection);
} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union or intersection type.');
} else {
throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
}
}
/**
* Returns the default value of parameter. If it is a constant, it returns its value.
* @return mixed
* @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved
*/
public static function getParameterDefaultValue(\ReflectionParameter $param)
{
if ($param->isDefaultValueConstant()) {
$const = $orig = $param->getDefaultValueConstantName();
$pair = explode('::', $const);
if (isset($pair[1])) {
$pair[0] = Type::resolve($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 a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the 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 a reflection of a method that contains a declaration of $method.
* Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name.
*/
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;
}
/**
* Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache.
*/
public static function areCommentsAvailable(): bool
{
static $res;
return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment();
}
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 the name of the class to full name in the given context of given class.
* Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
* @throws Nette\InvalidArgumentException
*/
public static function expandClassName(string $name, \ReflectionClass $context): 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' || $lower === 'static') {
return $context->name;
} elseif ($lower === 'parent') {
return $context->getParentClass()
? $context->getParentClass()->name
: 'parent';
} elseif ($name[0] === '\\') { // fully qualified name
return ltrim($name, '\\');
}
$uses = self::getUseStatements($context);
$parts = explode('\\', $name, 2);
if (isset($uses[$parts[0]])) {
$parts[0] = $uses[$parts[0]];
return implode('\\', $parts);
} elseif ($context->inNamespace()) {
return $context->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 = [];
$nameTokens = PHP_VERSION_ID < 80000
? [T_STRING, T_NS_SEPARATOR]
: [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED];
while ($token = current($tokens)) {
next($tokens);
switch (is_array($token) ? $token[0] : $token) {
case T_NAMESPACE:
$namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\');
$uses = [];
break;
case T_CLASS:
case T_INTERFACE:
case T_TRAIT:
case PHP_VERSION_ID < 80100
? T_CLASS
: T_ENUM:
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, $nameTokens))) {
$name = ltrim($name, '\\');
if (self::fetch($tokens, '{')) {
while ($suffix = self::fetch($tokens, $nameTokens)) {
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;
}
}

569
vendor/nette/utils/src/Utils/Strings.php vendored Normal file
View File

@@ -0,0 +1,569 @@
<?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 in UTF-8 encoding.
*/
public static function checkEncoding(string $s): bool
{
return $s === self::fixEncoding($s);
}
/**
* Removes all invalid UTF-8 characters from a 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 (number in range 0x0000..D7FF or 0xE000..10FFFF).
* @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 specified by starting position and length. If start is negative,
* the returned string will start at the start'th character from the end of 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 control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines,
* trims end spaces on lines, normalizes UTF-8 to the normal form of NFC.
*/
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, ie removes diacritics etc.
*/
public static function toAscii(string $s): string
{
$iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null;
static $transliterator = null;
if ($transliterator === null) {
if (class_exists('Transliterator', false)) {
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
} else {
trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE);
$transliterator = false;
}
}
// 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', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß
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;
}
/**
* Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters
* except letters of the English alphabet and numbers with a hyphens.
*/
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 a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated,
* an ellipsis (or something else set with third argument) is appended to the string.
*/
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 a multiline text from the left. Second argument sets how many indentation chars should be used,
* while the indent itself is the third argument (*tab* by default).
*/
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 all characters of UTF-8 string to lower case.
*/
public static function lower(string $s): string
{
return mb_strtolower($s, 'UTF-8');
}
/**
* Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged.
*/
public static function firstLower(string $s): string
{
return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1);
}
/**
* Converts all characters of a UTF-8 string to upper case.
*/
public static function upper(string $s): string
{
return mb_strtoupper($s, 'UTF-8');
}
/**
* Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged.
*/
public static function firstUpper(string $s): string
{
return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
}
/**
* Converts the first character of every word of a UTF-8 string to upper case and the others to lower case.
*/
public static function capitalize(string $s): string
{
return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
}
/**
* Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared,
* if it is negative, the corresponding number of characters from the end of the strings is compared,
* otherwise the appropriate number of characters from the beginning is compared.
*/
public static function compare(string $left, string $right, ?int $length = 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 ($length < 0) {
$left = self::substring($left, $length, -$length);
$right = self::substring($right, $length, -$length);
} elseif ($length !== null) {
$left = self::substring($left, 0, $length);
$right = self::substring($right, 0, $length);
}
return self::lower($left) === self::lower($right);
}
/**
* Finds the common prefix of strings or returns empty string if the prefix was not found.
* @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));
}
/**
* Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string.
*/
public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string
{
$charlist = preg_quote($charlist, '#');
return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '');
}
/**
* Pads a UTF-8 string to given length by prepending the $pad string to the beginning.
*/
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;
}
/**
* Pads UTF-8 string to given length by appending the $pad string to the end.
*/
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);
}
/**
* Reverses UTF-8 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 or returns null if the needle was not found.
* Negative value means searching from the end.
*/
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 or returns null if the needle was not found.
* Negative value means searching from the end.
*/
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 in characters of $nth occurence of $needle in $haystack or null if the $needle was not found.
* Negative value of `$nth` means searching from the end.
*/
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 in characters of $nth occurence of $needle in $haystack 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;
} elseif ($len === 0) {
return null;
}
$pos = $len - 1;
while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) {
$pos--;
}
}
return Helpers::falseToNull($pos);
}
/**
* Splits a string into array by the regular expression. Parenthesized expression in the delimiter are captured.
* Parameter $flags can be any combination of PREG_SPLIT_NO_EMPTY and PREG_OFFSET_CAPTURE flags.
*/
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]);
}
/**
* Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern.
* Parameter $flags can be any combination of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL flags.
*/
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;
}
/**
* Finds all occurrences matching regular expression pattern and returns a two-dimensional array. Result is array of matches (ie uses by default PREG_SET_ORDER).
* Parameter $flags can be any combination of PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL and PREG_PATTERN_ORDER flags.
*/
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;
}
/**
* Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
* @param string|array $pattern
* @param string|callable $replacement
*/
public static function replace(string $subject, $pattern, $replacement = '', 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 (is_array($pattern) && is_string(key($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;
}
}

252
vendor/nette/utils/src/Utils/Type.php vendored Normal file
View File

@@ -0,0 +1,252 @@
<?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 type reflection.
*/
final class Type
{
/** @var array */
private $types;
/** @var bool */
private $single;
/** @var string |, & */
private $kind;
/**
* Creates a Type object based on reflection. Resolves self, static and parent to the actual class name.
* If the subject has no type, it returns null.
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection
*/
public static function fromReflection($reflection): ?self
{
if ($reflection instanceof \ReflectionProperty && PHP_VERSION_ID < 70400) {
return null;
} elseif ($reflection instanceof \ReflectionMethod) {
$type = $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null);
} else {
$type = $reflection instanceof \ReflectionFunctionAbstract
? $reflection->getReturnType()
: $reflection->getType();
}
if ($type === null) {
return null;
} elseif ($type instanceof \ReflectionNamedType) {
$name = self::resolve($type->getName(), $reflection);
return new self($type->allowsNull() && $type->getName() !== 'mixed' ? [$name, 'null'] : [$name]);
} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
return new self(
array_map(
function ($t) use ($reflection) { return self::resolve($t->getName(), $reflection); },
$type->getTypes()
),
$type instanceof \ReflectionUnionType ? '|' : '&'
);
} else {
throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($reflection));
}
}
/**
* Creates the Type object according to the text notation.
*/
public static function fromString(string $type): self
{
if (!preg_match('#(?:
\?([\w\\\\]+)|
[\w\\\\]+ (?: (&[\w\\\\]+)* | (\|[\w\\\\]+)* )
)()$#xAD', $type, $m)) {
throw new Nette\InvalidArgumentException("Invalid type '$type'.");
}
[, $nType, $iType] = $m;
if ($nType) {
return new self([$nType, 'null']);
} elseif ($iType) {
return new self(explode('&', $type), '&');
} else {
return new self(explode('|', $type));
}
}
/**
* Resolves 'self', 'static' and 'parent' to the actual class name.
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection
*/
public static function resolve(string $type, $reflection): string
{
$lower = strtolower($type);
if ($reflection instanceof \ReflectionFunction) {
return $type;
} elseif ($lower === 'self' || $lower === 'static') {
return $reflection->getDeclaringClass()->name;
} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
return $reflection->getDeclaringClass()->getParentClass()->name;
} else {
return $type;
}
}
private function __construct(array $types, string $kind = '|')
{
if ($types[0] === 'null') { // null as last
array_push($types, array_shift($types));
}
$this->types = $types;
$this->single = ($types[1] ?? 'null') === 'null';
$this->kind = count($types) > 1 ? $kind : '';
}
public function __toString(): string
{
return $this->single
? (count($this->types) > 1 ? '?' : '') . $this->types[0]
: implode($this->kind, $this->types);
}
/**
* Returns the array of subtypes that make up the compound type as strings.
* @return string[]
*/
public function getNames(): array
{
return $this->types;
}
/**
* Returns the array of subtypes that make up the compound type as Type objects:
* @return self[]
*/
public function getTypes(): array
{
return array_map(function ($name) { return self::fromString($name); }, $this->types);
}
/**
* Returns the type name for single types, otherwise null.
*/
public function getSingleName(): ?string
{
return $this->single
? $this->types[0]
: null;
}
/**
* Returns true whether it is a union type.
*/
public function isUnion(): bool
{
return $this->kind === '|';
}
/**
* Returns true whether it is an intersection type.
*/
public function isIntersection(): bool
{
return $this->kind === '&';
}
/**
* Returns true whether it is a single type. Simple nullable types are also considered to be single types.
*/
public function isSingle(): bool
{
return $this->single;
}
/**
* Returns true whether the type is both a single and a PHP built-in type.
*/
public function isBuiltin(): bool
{
return $this->single && Reflection::isBuiltinType($this->types[0]);
}
/**
* Returns true whether the type is both a single and a class name.
*/
public function isClass(): bool
{
return $this->single && !Reflection::isBuiltinType($this->types[0]);
}
/**
* Determines if type is special class name self/parent/static.
*/
public function isClassKeyword(): bool
{
return $this->single && Reflection::isClassKeyword($this->types[0]);
}
/**
* Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
*/
public function allows(string $type): bool
{
if ($this->types === ['mixed']) {
return true;
}
$type = self::fromString($type);
if ($this->isIntersection()) {
if (!$type->isIntersection()) {
return false;
}
return Arrays::every($this->types, function ($currentType) use ($type) {
$builtin = Reflection::isBuiltinType($currentType);
return Arrays::some($type->types, function ($testedType) use ($currentType, $builtin) {
return $builtin
? strcasecmp($currentType, $testedType) === 0
: is_a($testedType, $currentType, true);
});
});
}
$method = $type->isIntersection() ? 'some' : 'every';
return Arrays::$method($type->types, function ($testedType) {
$builtin = Reflection::isBuiltinType($testedType);
return Arrays::some($this->types, function ($currentType) use ($testedType, $builtin) {
return $builtin
? strcasecmp($currentType, $testedType) === 0
: is_a($testedType, $currentType, true);
});
});
}
}

View File

@@ -0,0 +1,383 @@
<?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' => [self::class, 'isCallable'],
'iterable' => 'is_iterable',
'list' => [Arrays::class, 'isList'],
'mixed' => [self::class, 'isMixed'],
'none' => [self::class, 'isNone'],
'number' => [self::class, 'isNumber'],
'numeric' => [self::class, 'isNumeric'],
'numericint' => [self::class, 'isNumericInt'],
// string patterns
'alnum' => 'ctype_alnum',
'alpha' => 'ctype_alpha',
'digit' => 'ctype_digit',
'lower' => 'ctype_lower',
'pattern' => null,
'space' => 'ctype_space',
'unicode' => [self::class, 'isUnicode'],
'upper' => 'ctype_upper',
'xdigit' => 'ctype_xdigit',
// syntax validation
'email' => [self::class, 'isEmail'],
'identifier' => [self::class, 'isPhpIdentifier'],
'uri' => [self::class, 'isUri'],
'url' => [self::class, 'isUrl'],
// environment validation
'class' => 'class_exists',
'interface' => 'interface_exists',
'directory' => 'is_dir',
'file' => 'is_file',
'type' => [self::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',
];
/**
* Verifies that the value is of expected types separated by pipe.
* @param mixed $value
* @throws AssertionException
*/
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.");
}
}
/**
* Verifies that element $key in array is of expected types separated by pipe.
* @param mixed[] $array
* @param int|string $key
* @throws AssertionException
*/
public static function assertField(
array $array,
$key,
?string $expected = null,
string $label = "item '%' in array"
): void {
if (!array_key_exists($key, $array)) {
throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
} elseif ($expected) {
static::assert($array[$key], $expected, str_replace('%', $key, $label));
}
}
/**
* Verifies that the value is of expected types 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 types 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;
}
/**
* Checks if the value is an integer or a float.
* @param mixed $value
*/
public static function isNumber($value): bool
{
return is_int($value) || is_float($value);
}
/**
* Checks if the value is an integer or a integer written in a string.
* @param mixed $value
*/
public static function isNumericInt($value): bool
{
return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
}
/**
* Checks if the value is a number or a number written in a string.
* @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]*|\.[0-9]+)$#D', $value));
}
/**
* Checks if the value is a syntactically correct callback.
* @param mixed $value
*/
public static function isCallable($value): bool
{
return $value && is_callable($value, true);
}
/**
* Checks if the value is a valid UTF-8 string.
* @param mixed $value
*/
public static function isUnicode($value): bool
{
return is_string($value) && preg_match('##u', $value);
}
/**
* Checks if the value is 0, '', false or null.
* @param mixed $value
*/
public static function isNone($value): bool
{
return $value == null; // intentionally ==
}
/** @internal */
public static function isMixed(): bool
{
return true;
}
/**
* Checks if a variable is a zero-based integer indexed array.
* @param mixed $value
* @deprecated use Nette\Utils\Arrays::isList
*/
public static function isList($value): bool
{
return Arrays::isList($value);
}
/**
* Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
* Numbers, strings and DateTime objects can be compared.
* @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]));
}
/**
* Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
*/
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);
}
/**
* Checks if the value is a valid URL address.
*/
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);
}
/**
* Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
*/
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 preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value) === 1;
}
}

View File

@@ -0,0 +1,58 @@
<?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;
/**
* 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
{
}

View File

@@ -0,0 +1,32 @@
<?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;
if (false) {
/** @deprecated use Nette\HtmlStringable */
interface IHtmlString extends Nette\HtmlStringable
{
}
} elseif (!interface_exists(IHtmlString::class)) {
class_alias(Nette\HtmlStringable::class, IHtmlString::class);
}
namespace Nette\Localization;
if (false) {
/** @deprecated use Nette\Localization\Translator */
interface ITranslator extends Translator
{
}
} elseif (!interface_exists(ITranslator::class)) {
class_alias(Translator::class, ITranslator::class);
}

109
vendor/nette/utils/src/exceptions.php vendored Normal file
View File

@@ -0,0 +1,109 @@
<?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
{
}