v1.2.0
This commit is contained in:
parent
693e5ae2e6
commit
7d3fc1969d
52 changed files with 1776 additions and 295 deletions
2
vendor/nette/utils/composer.json
vendored
2
vendor/nette/utils/composer.json
vendored
|
|
@ -30,7 +30,7 @@
|
|||
"nette/schema": "<1.2.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
|
||||
"ext-iconv": "to use Strings::chr(), ord() 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...",
|
||||
|
|
|
|||
8
vendor/nette/utils/src/Utils/ArrayList.php
vendored
8
vendor/nette/utils/src/Utils/ArrayList.php
vendored
|
|
@ -8,7 +8,7 @@
|
|||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function array_slice, array_splice, count, is_int;
|
||||
use function array_splice, array_unshift, count, is_int;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -123,8 +123,8 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
|||
*/
|
||||
public function prepend(mixed $value): void
|
||||
{
|
||||
$first = array_slice($this->list, 0, 1);
|
||||
$this->offsetSet(0, $value);
|
||||
array_splice($this->list, 1, 0, $first);
|
||||
// route the value through offsetSet() first so a validation added in a subclass isn't bypassed
|
||||
$this->offsetSet(null, $value);
|
||||
array_unshift($this->list, ...array_splice($this->list, -1));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
vendor/nette/utils/src/Utils/Arrays.php
vendored
2
vendor/nette/utils/src/Utils/Arrays.php
vendored
|
|
@ -304,7 +304,7 @@ class Arrays
|
|||
{
|
||||
$parts = is_array($path)
|
||||
? $path
|
||||
: preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
: 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 '" . (is_array($path) ? implode('', $path) : $path) . "'.");
|
||||
|
|
|
|||
187
vendor/nette/utils/src/Utils/DateTimeImmutable.php
vendored
Normal file
187
vendor/nette/utils/src/Utils/DateTimeImmutable.php
vendored
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use function array_merge, checkdate, implode, is_int, is_string, preg_match, preg_replace_callback, sprintf, trim;
|
||||
|
||||
|
||||
/**
|
||||
* Extends PHP's DateTimeImmutable with strict validation and additional factory methods.
|
||||
* Invalid dates and times are rejected with an exception instead of being silently adjusted.
|
||||
* All modifications return a new instance, the original object never changes.
|
||||
*/
|
||||
class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
|
||||
{
|
||||
/** matches relative sub-day parts (minutes, seconds, ...) that must be applied in UTC to be DST-safe */
|
||||
private const RelativePattern = '/[+-]?\s*\d+\s+((microsecond|millisecond|[mµu]sec)s?|[mµ]s|sec(ond)?s?|min(ute)?s?|hours?)(\s+ago)?\b/iu';
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DateTimeImmutable object from a string, UNIX timestamp, or other DateTimeInterface object.
|
||||
* @throws \Exception if the date and time are not valid.
|
||||
*/
|
||||
public static function from(string|int|\DateTimeInterface|null $time): static
|
||||
{
|
||||
if ($time instanceof \DateTimeInterface) {
|
||||
return static::createFromInterface($time);
|
||||
} elseif (is_int($time)) {
|
||||
return (new static)->setTimestamp($time);
|
||||
} else { // textual or null
|
||||
return new static((string) $time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates DateTimeImmutable object.
|
||||
* @throws \Exception 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,
|
||||
): static
|
||||
{
|
||||
$sec = (int) floor($second);
|
||||
return (new static)
|
||||
->setDate($year, $month, $day)
|
||||
->setTime($hour, $minute, $sec, (int) round(($second - $sec) * 1e6));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new DateTimeImmutable object formatted according to the specified format.
|
||||
*/
|
||||
public static function createFromFormat(
|
||||
string $format,
|
||||
string $datetime,
|
||||
string|\DateTimeZone|null $timezone = null,
|
||||
): static|false
|
||||
{
|
||||
if (is_string($timezone)) {
|
||||
$timezone = new \DateTimeZone($timezone);
|
||||
}
|
||||
|
||||
$date = parent::createFromFormat($format, $datetime, $timezone);
|
||||
return $date ? static::from($date) : false;
|
||||
}
|
||||
|
||||
|
||||
public function __construct(string $datetime = 'now', ?\DateTimeZone $timezone = null)
|
||||
{
|
||||
if (preg_match(self::RelativePattern, $datetime)) {
|
||||
// sub-day relative parts must be applied in UTC, which cannot happen inside a constructor => resolve & re-parse
|
||||
$result = self::resolve(null, $datetime, $timezone);
|
||||
parent::__construct($result->format('Y-m-d H:i:s.u'), $result->getTimezone());
|
||||
} else {
|
||||
parent::__construct($datetime, $timezone);
|
||||
self::handleErrors($datetime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function modify(string $modifier): static
|
||||
{
|
||||
return static::createFromInterface(self::resolve($this, $modifier, null));
|
||||
}
|
||||
|
||||
|
||||
public function setDate(int $year, int $month, int $day): static
|
||||
{
|
||||
if (!checkdate($month, $day, $year)) {
|
||||
throw new \Exception(sprintf('The date %04d-%02d-%02d is not valid.', $year, $month, $day));
|
||||
}
|
||||
return parent::setDate($year, $month, $day);
|
||||
}
|
||||
|
||||
|
||||
public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static
|
||||
{
|
||||
if (
|
||||
$hour < 0 || $hour > 23
|
||||
|| $minute < 0 || $minute > 59
|
||||
|| $second < 0 || $second >= 60
|
||||
|| $microsecond < 0 || $microsecond >= 1_000_000
|
||||
) {
|
||||
throw new \Exception(sprintf('The time %02d:%02d:%08.5F is not valid.', $hour, $minute, $second + $microsecond / 1_000_000));
|
||||
}
|
||||
return parent::setTime($hour, $minute, $second, $microsecond);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Splits the input into absolute and relative parts and returns the resulting instant. Relative sub-day
|
||||
* parts are applied in UTC so that crossing a DST boundary does not shift the result.
|
||||
*/
|
||||
private static function resolve(?\DateTimeInterface $base, string $input, ?\DateTimeZone $timezone): \DateTimeImmutable
|
||||
{
|
||||
$relPart = '';
|
||||
$absPart = preg_replace_callback(
|
||||
self::RelativePattern,
|
||||
function ($m) use (&$relPart) {
|
||||
$relPart .= $m[0] . ' ';
|
||||
return '';
|
||||
},
|
||||
$input,
|
||||
);
|
||||
|
||||
if ($base === null) {
|
||||
$result = new \DateTimeImmutable($absPart, $timezone);
|
||||
self::handleErrors($input);
|
||||
} else {
|
||||
$result = \DateTimeImmutable::createFromInterface($base);
|
||||
if (trim($absPart) !== '') {
|
||||
$modified = @$result->modify($absPart); // @ - on PHP 8.2 an invalid modifier emits a warning and returns false instead of throwing; handleErrors() turns it into an exception
|
||||
self::handleErrors($input);
|
||||
$result = $modified ?: $result;
|
||||
}
|
||||
}
|
||||
|
||||
if ($relPart !== '') {
|
||||
$timezone ??= $result->getTimezone();
|
||||
$result = $result
|
||||
->setTimezone(new \DateTimeZone('UTC'))
|
||||
->modify($relPart)
|
||||
->setTimezone($timezone);
|
||||
self::handleErrors($input);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
|
||||
private static function handleErrors(string $value): void
|
||||
{
|
||||
$errors = self::getLastErrors();
|
||||
$errors = array_merge($errors['errors'] ?? [], $errors['warnings'] ?? []);
|
||||
if ($errors) {
|
||||
throw new \Exception(implode(', ', $errors) . " '$value'");
|
||||
}
|
||||
}
|
||||
}
|
||||
32
vendor/nette/utils/src/Utils/FileSystem.php
vendored
32
vendor/nette/utils/src/Utils/FileSystem.php
vendored
|
|
@ -8,7 +8,7 @@
|
|||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function array_pop, chmod, decoct, dirname, end, fclose, file_exists, file_get_contents, file_put_contents, fopen, implode, is_dir, is_file, is_link, mkdir, preg_match, preg_split, realpath, rename, rmdir, rtrim, sprintf, str_replace, stream_copy_to_stream, stream_is_local, strtr;
|
||||
use function array_pop, chmod, decoct, dirname, end, fclose, file_exists, file_get_contents, file_put_contents, fopen, implode, is_dir, is_file, is_link, mkdir, preg_match, preg_split, realpath, rename, rmdir, rtrim, sprintf, str_replace, stream_copy_to_stream, stream_is_local, strtr, uniqid, unlink, usleep;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
|
||||
|
|
@ -233,6 +233,36 @@ final class FileSystem
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the string to a file atomically: the content is written to a temporary file, which then replaces
|
||||
* the target, so a concurrent reader never sees the file partially written or truncated.
|
||||
* Creates the parent directory if it does not exist. Pass null as $mode to skip chmod.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function writeAtomic(string $file, string $content, ?int $mode = 0o666): void
|
||||
{
|
||||
$file = realpath($file) ?: $file; // writes through a symlink to its target, as write() does
|
||||
$tmp = $file . '.' . uniqid('', more_entropy: true) . '.tmp';
|
||||
try {
|
||||
static::write($tmp, $content, $mode);
|
||||
// plain rename() is atomic; static::rename() must not be used here, it deletes the target first
|
||||
for ($i = 0; !@rename($tmp, $file); $i++) { // @ is escalated to exception
|
||||
if (!Helpers::IsWindows || $i >= 20) {
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to write file '%s'. %s",
|
||||
self::normalizePath($file),
|
||||
Helpers::getLastError(),
|
||||
));
|
||||
}
|
||||
usleep(5_000); // on Windows, rename fails while the target is open in another process
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@unlink($tmp);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets file permissions to `$fileMode` or directory permissions to `$dirMode`.
|
||||
* Recursively traverses and sets permissions on the entire contents of the directory as well.
|
||||
|
|
|
|||
83
vendor/nette/utils/src/Utils/Finder.php
vendored
83
vendor/nette/utils/src/Utils/Finder.php
vendored
|
|
@ -8,8 +8,8 @@
|
|||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function array_merge, count, func_get_args, func_num_args, glob, implode, is_array, is_dir, iterator_to_array, preg_match, preg_quote, preg_replace, preg_split, rtrim, spl_object_id, sprintf, str_ends_with, str_starts_with, strnatcmp, strpbrk, strrpos, strtolower, strtr, substr, usort;
|
||||
use const GLOB_NOESCAPE, GLOB_NOSORT, GLOB_ONLYDIR;
|
||||
use function array_filter, array_merge, array_values, count, func_get_args, func_num_args, glob, implode, is_array, is_dir, iterator_to_array, preg_match, preg_quote, preg_replace, preg_split, rtrim, spl_object_id, sprintf, str_starts_with, strnatcmp, strpbrk, strrpos, strtolower, strtr, substr, trigger_error, usort;
|
||||
use const DIRECTORY_SEPARATOR, E_USER_DEPRECATED, GLOB_NOESCAPE, GLOB_NOSORT, GLOB_ONLYDIR;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -47,18 +47,19 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Begins search for files and directories matching mask.
|
||||
* Begins search for files and directories matching mask. The ** wildcard searches recursively; a trailing slash limits the mask to directories.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function find(string|array $masks = ['*']): static
|
||||
{
|
||||
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
|
||||
return (new static)->addMask($masks, 'dir')->addMask($masks, 'file');
|
||||
$files = array_filter($masks, fn(string $mask): bool => !self::hasTrailingSeparator($mask)); // trailing slash means directories only
|
||||
return (new static)->addMask($masks, 'dir')->addMask(array_values($files), 'file');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins search for files matching mask.
|
||||
* Begins search for files matching mask. The ** wildcard searches recursively.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function findFiles(string|array $masks = ['*']): static
|
||||
|
|
@ -69,7 +70,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Begins search for directories matching mask.
|
||||
* Begins search for directories matching mask. The ** wildcard searches recursively.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function findDirectories(string|array $masks = ['*']): static
|
||||
|
|
@ -103,24 +104,34 @@ class Finder implements \IteratorAggregate
|
|||
private function addMask(array $masks, string $mode): static
|
||||
{
|
||||
foreach ($masks as $mask) {
|
||||
$mask = FileSystem::unixSlashes($mask);
|
||||
$orig = $mask;
|
||||
if ($mode === 'dir') {
|
||||
$mask = rtrim($mask, '/');
|
||||
$mask = rtrim($mask, '/\\');
|
||||
}
|
||||
if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) {
|
||||
throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
|
||||
if ($mask === '' || ($mode === 'file' && self::hasTrailingSeparator($mask))) {
|
||||
throw new Nette\InvalidArgumentException("Invalid mask '$orig'");
|
||||
}
|
||||
if (str_starts_with($mask, '**/')) {
|
||||
$mask = substr($mask, 3);
|
||||
}
|
||||
$this->find[] = [$mask, $mode];
|
||||
$this->find[] = [self::expandGlobStar($mask), $mode];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
private static function hasTrailingSeparator(string $mask): bool
|
||||
{
|
||||
return ($last = substr($mask, -1)) === '/' || $last === '\\';
|
||||
}
|
||||
|
||||
|
||||
// Expands a ** that is not followed by a slash into **/*, so that e.g. "test/**" and "**.c" search recursively.
|
||||
private static function expandGlobStar(string $mask): string
|
||||
{
|
||||
return preg_replace('~(?<=^|[/\\\])\*\*(?![/\\\])~', '**/*', $mask);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches in the given directories. Wildcards are allowed.
|
||||
* Searches in the given directories. Wildcards * and ? are allowed; unlike in masks, [ and ] are taken literally.
|
||||
* @param string|list<string> $paths
|
||||
*/
|
||||
public function in(string|array $paths): static
|
||||
|
|
@ -132,13 +143,13 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Searches recursively from the given directories. Wildcards are allowed.
|
||||
* Searches recursively from the given directories. Wildcards * and ? are allowed; unlike in masks, [ and ] are taken literally.
|
||||
* @param string|list<string> $paths
|
||||
*/
|
||||
public function from(string|array $paths): static
|
||||
{
|
||||
$paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic
|
||||
$this->addLocation($paths, '/**');
|
||||
$this->addLocation($paths, DIRECTORY_SEPARATOR . '**');
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +161,7 @@ class Finder implements \IteratorAggregate
|
|||
if ($path === '') {
|
||||
throw new Nette\InvalidArgumentException("Invalid directory '$path'");
|
||||
}
|
||||
$path = rtrim(FileSystem::unixSlashes($path), '/');
|
||||
$path = rtrim($path, '/\\');
|
||||
$this->in[] = $path . $ext;
|
||||
}
|
||||
}
|
||||
|
|
@ -216,24 +227,29 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Skips entries that matches the given masks relative to the ones defined with the in() or from() methods.
|
||||
* Skips entries that match the given masks, using the same grammar as find() masks, relative to the directories from in() or from().
|
||||
* A trailing slash excludes directories only; a trailing /* or /** excludes the contents while keeping the directory itself.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public function exclude(string|array $masks): static
|
||||
{
|
||||
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
|
||||
foreach ($masks as $mask) {
|
||||
$orig = $mask;
|
||||
$mask = FileSystem::unixSlashes($mask);
|
||||
if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) {
|
||||
throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
|
||||
if (FileSystem::isAbsolute($mask) || $mask === '..' || str_starts_with($mask, '../')) {
|
||||
trigger_error("Absolute or ../ mask '$orig' in exclude() is deprecated and will change meaning, use a mask relative to the searched directory.", E_USER_DEPRECATED);
|
||||
}
|
||||
if (!preg_match('~^/?(\*\*/)?(.+?)(/\*\*|/\*|/|)$~D', $mask, $m)) {
|
||||
throw new Nette\InvalidArgumentException("Invalid mask '$orig'");
|
||||
}
|
||||
$end = $m[3];
|
||||
$re = $this->buildPattern($m[2]);
|
||||
$re = $this->buildPattern(self::expandGlobStar($m[2]));
|
||||
$filter = fn(FileInfo $file): bool => ($end && !$file->isDir())
|
||||
|| !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname()));
|
||||
|
||||
$this->descentFilter($filter);
|
||||
if ($end !== '/*') {
|
||||
if ($end === '' || $end === '/') {
|
||||
$this->filter($filter);
|
||||
}
|
||||
}
|
||||
|
|
@ -340,7 +356,6 @@ class Finder implements \IteratorAggregate
|
|||
if ($item instanceof self) {
|
||||
yield from $item->getIterator();
|
||||
} else {
|
||||
$item = FileSystem::platformSlashes($item);
|
||||
yield $item => new FileInfo($item);
|
||||
}
|
||||
}
|
||||
|
|
@ -361,7 +376,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
try {
|
||||
$pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS);
|
||||
$pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
if ($this->ignoreUnreadableDirs) {
|
||||
return;
|
||||
|
|
@ -370,7 +385,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
}
|
||||
|
||||
$files = $this->convertToFiles($pathNames, implode('/', $subdirs), FileSystem::isAbsolute($dir));
|
||||
$files = $this->convertToFiles($pathNames, implode(DIRECTORY_SEPARATOR, $subdirs), FileSystem::isAbsolute($dir));
|
||||
|
||||
if ($this->sort) {
|
||||
$files = iterator_to_array($files);
|
||||
|
|
@ -417,9 +432,8 @@ class Finder implements \IteratorAggregate
|
|||
{
|
||||
foreach ($pathNames as $pathName) {
|
||||
if (!$absolute) {
|
||||
$pathName = preg_replace('~\.?/~A', '', $pathName);
|
||||
$pathName = preg_replace('~\.?[\\\/]~A', '', $pathName);
|
||||
}
|
||||
$pathName = FileSystem::platformSlashes($pathName);
|
||||
yield new FileInfo($pathName, $relativePath);
|
||||
}
|
||||
}
|
||||
|
|
@ -457,7 +471,7 @@ class Finder implements \IteratorAggregate
|
|||
} else {
|
||||
foreach ($this->in ?: ['.'] as $in) {
|
||||
$in = strtr($in, ['[' => '[[]', ']' => '[]]']); // in path, do not treat [ and ] as a pattern by glob()
|
||||
$splits[] = self::splitRecursivePart($in . '/' . $mask);
|
||||
$splits[] = self::splitRecursivePart($in . DIRECTORY_SEPARATOR . $mask);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -488,11 +502,13 @@ class Finder implements \IteratorAggregate
|
|||
*/
|
||||
private static function splitRecursivePart(string $path): array
|
||||
{
|
||||
$a = strrpos($path, '/');
|
||||
$parts = preg_split('~(?<=^|/)\*\*($|/)~', substr($path, 0, $a + 1), 2);
|
||||
$pos = strrpos(strtr($path, '\\', '/'), '/');
|
||||
$dir = $pos === false ? '' : substr($path, 0, $pos + 1);
|
||||
$file = $pos === false ? $path : substr($path, $pos + 1);
|
||||
$parts = preg_split('~(?<=^|[\\\/])\*\*($|[\\\/])~', $dir, 2);
|
||||
return isset($parts[1])
|
||||
? [$parts[0], $parts[1] . substr($path, $a + 1), true]
|
||||
: [$parts[0], substr($path, $a + 1), false];
|
||||
? [$parts[0], $parts[1] . $file, true]
|
||||
: [$parts[0], $file, false];
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -501,6 +517,7 @@ class Finder implements \IteratorAggregate
|
|||
*/
|
||||
private function buildPattern(string $mask): string
|
||||
{
|
||||
$mask = FileSystem::unixSlashes($mask);
|
||||
if ($mask === '*') {
|
||||
return '##';
|
||||
} elseif (str_starts_with($mask, './')) {
|
||||
|
|
|
|||
56
vendor/nette/utils/src/Utils/Html.php
vendored
56
vendor/nette/utils/src/Utils/Html.php
vendored
|
|
@ -8,8 +8,8 @@
|
|||
namespace Nette\Utils;
|
||||
|
||||
use Nette\HtmlStringable;
|
||||
use function array_merge, array_splice, count, explode, func_num_args, html_entity_decode, htmlspecialchars, http_build_query, implode, is_array, is_bool, is_float, is_object, is_string, json_encode, max, number_format, rtrim, str_contains, str_repeat, str_replace, strip_tags, strncmp, strpbrk, substr;
|
||||
use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
|
||||
use function array_merge, array_splice, count, explode, func_num_args, html_entity_decode, htmlspecialchars, http_build_query, implode, is_array, is_bool, is_float, is_object, is_string, json_encode, max, number_format, rtrim, str_contains, str_repeat, str_replace, strip_tags, strncmp, strpbrk, substr, trigger_error, ucfirst;
|
||||
use const E_USER_DEPRECATED, ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -229,6 +229,9 @@ use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
|
|||
* @method self width(?int $val)
|
||||
* @method self wrap(?string $val)
|
||||
*
|
||||
* @method static static text(mixed $text)
|
||||
* @method static static html(mixed $html)
|
||||
*
|
||||
* @implements \IteratorAggregate<int, self|string>
|
||||
* @implements \ArrayAccess<int, self|string>
|
||||
*/
|
||||
|
|
@ -280,8 +283,19 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a nameless element (fragment) containing the given children.
|
||||
* Everything except HtmlStringable is escaped; use Html::html() for raw HTML. Nulls are skipped.
|
||||
*/
|
||||
public static function fragment(HtmlStringable|\Stringable|string|int|null ...$children): static
|
||||
{
|
||||
return (new static)->add(...$children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object representing HTML text.
|
||||
* @deprecated use Html::html()
|
||||
*/
|
||||
public static function fromHtml(string $html): static
|
||||
{
|
||||
|
|
@ -291,6 +305,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Returns an object representing plain text.
|
||||
* @deprecated use Html::text()
|
||||
*/
|
||||
public static function fromText(string $text): static
|
||||
{
|
||||
|
|
@ -473,6 +488,10 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
*/
|
||||
final public function __call(string $m, array $args): mixed
|
||||
{
|
||||
if ($m === 'text' || $m === 'html') {
|
||||
trigger_error("Method \$el->$m() is deprecated, use set" . ucfirst($m) . "() for content or setAttribute() for the '$m' attribute; Html::$m() is a static factory.", E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$p = substr($m, 0, 3);
|
||||
if ($p === 'get' || $p === 'set' || $p === 'add') {
|
||||
$m = substr($m, 3);
|
||||
|
|
@ -498,6 +517,20 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates element with escaped text (Html::text()) or raw HTML (Html::html()) content.
|
||||
* @param mixed[] $args
|
||||
*/
|
||||
final public static function __callStatic(string $name, array $args): static
|
||||
{
|
||||
return match ($name) {
|
||||
'text' => (new static)->setText(...$args),
|
||||
'html' => (new static)->setHtml(...$args),
|
||||
default => ObjectHelpers::strictStaticCall(static::class, $name),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Special setter for element's attribute.
|
||||
* @param array<string, mixed> $query
|
||||
|
|
@ -575,6 +608,21 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends the given children. Everything except HtmlStringable is escaped; use Html::html() for raw HTML. Nulls are skipped.
|
||||
*/
|
||||
public function add(HtmlStringable|\Stringable|string|int|null ...$children): static
|
||||
{
|
||||
foreach ($children as $child) {
|
||||
if ($child !== null) {
|
||||
$this->addText($child);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new element's child.
|
||||
*/
|
||||
|
|
@ -813,8 +861,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
$q = str_contains($value, '"') ? "'" : '"';
|
||||
$s .= ' ' . $key . '=' . $q
|
||||
. str_replace(
|
||||
['&', $q, '<'],
|
||||
['&', $q === '"' ? '"' : ''', '<'],
|
||||
['&', $q],
|
||||
['&', $q === '"' ? '"' : '''],
|
||||
$value,
|
||||
)
|
||||
. (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '')
|
||||
|
|
|
|||
4
vendor/nette/utils/src/Utils/Image.php
vendored
4
vendor/nette/utils/src/Utils/Image.php
vendored
|
|
@ -327,7 +327,7 @@ class Image
|
|||
ImageType::PNG => IMG_PNG,
|
||||
ImageType::GIF => IMG_GIF,
|
||||
ImageType::WEBP => IMG_WEBP,
|
||||
ImageType::AVIF => 256, // IMG_AVIF,
|
||||
ImageType::AVIF => IMG_AVIF,
|
||||
ImageType::BMP => IMG_BMP,
|
||||
default => 0,
|
||||
});
|
||||
|
|
@ -347,7 +347,7 @@ class Image
|
|||
$flag & IMG_JPG ? ImageType::JPEG : null,
|
||||
$flag & IMG_PNG ? ImageType::PNG : null,
|
||||
$flag & IMG_WEBP ? ImageType::WEBP : null,
|
||||
$flag & 256 ? ImageType::AVIF : null, // IMG_AVIF
|
||||
$flag & IMG_AVIF ? ImageType::AVIF : null,
|
||||
$flag & IMG_BMP ? ImageType::BMP : null,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
2
vendor/nette/utils/src/Utils/Iterables.php
vendored
2
vendor/nette/utils/src/Utils/Iterables.php
vendored
|
|
@ -195,7 +195,7 @@ final class Iterables
|
|||
return new class ($factory(...)) implements \IteratorAggregate {
|
||||
public function __construct(
|
||||
/** @var \Closure(): iterable<mixed, mixed> */
|
||||
private \Closure $factory,
|
||||
private readonly \Closure $factory,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
5
vendor/nette/utils/src/Utils/Json.php
vendored
5
vendor/nette/utils/src/Utils/Json.php
vendored
|
|
@ -8,7 +8,7 @@
|
|||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function defined, is_int, json_decode, json_encode, json_last_error, json_last_error_msg;
|
||||
use function is_int, json_decode, json_encode, json_last_error, json_last_error_msg;
|
||||
use const JSON_BIGINT_AS_STRING, JSON_FORCE_OBJECT, JSON_HEX_AMP, JSON_HEX_APOS, JSON_HEX_QUOT, JSON_HEX_TAG, JSON_OBJECT_AS_ARRAY, JSON_PRESERVE_ZERO_FRACTION, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE;
|
||||
|
||||
|
||||
|
|
@ -51,8 +51,7 @@ final class Json
|
|||
| ($htmlSafe ? JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG : 0);
|
||||
}
|
||||
|
||||
$flags |= JSON_UNESCAPED_SLASHES
|
||||
| (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7
|
||||
$flags |= JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION;
|
||||
|
||||
$json = json_encode($value, $flags);
|
||||
if ($error = json_last_error()) {
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ final class ObjectHelpers
|
|||
if ($rp->isPublic() && !$rp->isStatic()) {
|
||||
$prop = $name >= 'onA' && $name < 'on_' ? 'event' : true;
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
} catch (\ReflectionException) {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
83
vendor/nette/utils/src/Utils/Process.php
vendored
83
vendor/nette/utils/src/Utils/Process.php
vendored
|
|
@ -8,6 +8,8 @@
|
|||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function is_resource, is_string, strlen;
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -41,7 +43,11 @@ final class Process
|
|||
|
||||
/** @var array<int, true> Output IDs whose target resource was supplied by the caller and must not be closed here. */
|
||||
private array $callerOutputs = [];
|
||||
|
||||
/** @var array<int, true> Output IDs whose pipe is backed by a temporary file (Windows < 8.5 workaround). */
|
||||
private array $fileBackedOutputs = [];
|
||||
private float $startTime;
|
||||
private bool $detached = false;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -113,7 +119,7 @@ final class Process
|
|||
mixed $stdin,
|
||||
mixed $stdout,
|
||||
mixed $stderr,
|
||||
private ?float $timeout,
|
||||
private readonly ?float $timeout,
|
||||
) {
|
||||
$descriptors = [
|
||||
self::StdIn => $this->createInputDescriptor($stdin),
|
||||
|
|
@ -145,11 +151,32 @@ final class Process
|
|||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->detached) {
|
||||
return;
|
||||
}
|
||||
$this->outputBuffers = [];
|
||||
$this->terminate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detaches the process: it keeps running in the background and is not terminated when the object
|
||||
* is destroyed. STDIN and output pipes are closed, so the output must not be captured in memory
|
||||
* (pass a file name, a resource or false as $stdout/$stderr). Only the destructor behavior changes:
|
||||
* wait() and getExitCode() still block ($timeout still applies) and terminate() still kills it.
|
||||
* On POSIX, a detached child that exits early stays a zombie until the script ends.
|
||||
*/
|
||||
public function detach(): void
|
||||
{
|
||||
if ($this->outputBuffers !== []) {
|
||||
throw new Nette\InvalidStateException('Cannot detach process: its output is captured in memory, pass a file name, a resource or false as $stdout/$stderr.');
|
||||
}
|
||||
$this->detached = true;
|
||||
$this->closeStdInput();
|
||||
$this->closeOutputPipes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the process is currently running.
|
||||
*/
|
||||
|
|
@ -189,12 +216,12 @@ final class Process
|
|||
|
||||
/**
|
||||
* Reads any new data from the captured pipes into the buffers, so a process producing more output
|
||||
* than the OS pipe buffer holds does not block. (On Windows the captured output is a file and never blocks.)
|
||||
* than the OS pipe buffer holds does not block. With $final (process already finished) reads to EOF.
|
||||
*/
|
||||
private function drainPipes(): void
|
||||
private function drainPipes(bool $final = false): void
|
||||
{
|
||||
foreach ([self::StdOut, self::StdErr] as $id) {
|
||||
$this->readFromPipe($id);
|
||||
$this->readFromPipe($id, $final);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -397,16 +424,39 @@ final class Process
|
|||
* Reads any new data from the specified pipe and appends it to the buffer. Does nothing if the output
|
||||
* is not captured or the pipe is already closed (or handed over to another process).
|
||||
*/
|
||||
private function readFromPipe(int $id): void
|
||||
private function readFromPipe(int $id, bool $final = false): void
|
||||
{
|
||||
if (!isset($this->outputBuffers[$id]) || !is_resource($this->outputPipes[$id] ?? null)) {
|
||||
$pipe = $this->outputPipes[$id] ?? null;
|
||||
if (!isset($this->outputBuffers[$id]) || !is_resource($pipe)) {
|
||||
return;
|
||||
} elseif (Helpers::IsWindows) {
|
||||
fseek($this->outputPipes[$id], strlen($this->outputBuffers[$id]));
|
||||
|
||||
} elseif (isset($this->fileBackedOutputs[$id])) {
|
||||
// Windows < 8.5: captured output is a temporary file; read whatever was appended since last time
|
||||
fseek($pipe, strlen($this->outputBuffers[$id]));
|
||||
$this->outputBuffers[$id] .= stream_get_contents($pipe);
|
||||
|
||||
} elseif ($final) {
|
||||
// the process has finished, everything is buffered in the pipe; non-blocking read on POSIX
|
||||
// (cannot hang on a leaked descendant still holding the write end), blocking read to EOF on
|
||||
// Windows, where non-blocking mode does not work and stream_select() may miss buffered data
|
||||
stream_set_blocking($pipe, Helpers::IsWindows);
|
||||
$this->outputBuffers[$id] .= stream_get_contents($pipe);
|
||||
|
||||
} else {
|
||||
stream_set_blocking($this->outputPipes[$id], false);
|
||||
// non-blocking drain: stream_set_blocking(false) works on POSIX only, so reads are also
|
||||
// guarded by stream_select(), which works on Windows pipes since PHP 8.5 (PeekNamedPipe fix)
|
||||
stream_set_blocking($pipe, false);
|
||||
$read = [$pipe];
|
||||
$write = $except = [];
|
||||
while (@stream_select($read, $write, $except, 0, 0) > 0) {
|
||||
$chunk = fread($pipe, 8192);
|
||||
if ($chunk === false || $chunk === '') {
|
||||
break;
|
||||
}
|
||||
$this->outputBuffers[$id] .= $chunk;
|
||||
$read = [$pipe];
|
||||
}
|
||||
}
|
||||
$this->outputBuffers[$id] .= stream_get_contents($this->outputPipes[$id]);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -470,9 +520,12 @@ final class Process
|
|||
} elseif ($output === null) {
|
||||
$this->outputBuffers[$id] = '';
|
||||
$this->outputBufferOffsets[$id] = 0;
|
||||
// On Windows anonymous pipes are blocking and cannot be polled without freezing the process,
|
||||
// so captured output is backed by a temporary file that can be read non-blockingly (needed for timeouts).
|
||||
return Helpers::IsWindows ? tmpfile() : ['pipe', 'w'];
|
||||
if (Helpers::IsWindows && PHP_VERSION_ID < 80500) {
|
||||
// Windows < 8.5: stream_select() doesn't work on pipes, capture into a temp file that reads non-blockingly
|
||||
$this->fileBackedOutputs[$id] = true;
|
||||
return tmpfile();
|
||||
}
|
||||
return ['pipe', 'w'];
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Output must be string, resource, bool or null, ' . get_debug_type($output) . ' given.');
|
||||
|
|
@ -485,7 +538,7 @@ final class Process
|
|||
*/
|
||||
private function close(): void
|
||||
{
|
||||
$this->drainPipes();
|
||||
$this->drainPipes(final: true);
|
||||
$this->closeStdInput();
|
||||
$this->closeOutputPipes();
|
||||
proc_close($this->process);
|
||||
|
|
@ -494,7 +547,7 @@ final class Process
|
|||
|
||||
/**
|
||||
* Closes the output pipes that this class opened; resources supplied by the caller are left untouched.
|
||||
* (The temporary file backing captured output on Windows is removed by fclose() itself.)
|
||||
* (The temporary file backing captured output on Windows < 8.5 is removed by fclose() itself.)
|
||||
*/
|
||||
private function closeOutputPipes(): void
|
||||
{
|
||||
|
|
|
|||
2
vendor/nette/utils/src/Utils/Reflection.php
vendored
2
vendor/nette/utils/src/Utils/Reflection.php
vendored
|
|
@ -230,7 +230,7 @@ final class Reflection
|
|||
try {
|
||||
$tokens = \PhpToken::tokenize($code, TOKEN_PARSE);
|
||||
} catch (\ParseError $e) {
|
||||
trigger_error($e->getMessage(), E_USER_NOTICE);
|
||||
trigger_error($e->getMessage());
|
||||
$tokens = [];
|
||||
}
|
||||
|
||||
|
|
|
|||
10
vendor/nette/utils/src/Utils/Strings.php
vendored
10
vendor/nette/utils/src/Utils/Strings.php
vendored
|
|
@ -9,7 +9,7 @@ namespace Nette\Utils;
|
|||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
use Nette;
|
||||
use function array_keys, array_map, array_shift, array_values, bin2hex, class_exists, defined, extension_loaded, function_exists, htmlspecialchars, htmlspecialchars_decode, iconv, iconv_strlen, iconv_substr, implode, in_array, is_array, is_callable, is_int, is_object, is_string, key, max, mb_convert_case, mb_strlen, mb_strtolower, mb_strtoupper, mb_substr, pack, preg_last_error, preg_last_error_msg, preg_quote, preg_replace, str_contains, str_ends_with, str_repeat, str_replace, str_starts_with, strlen, strpos, strrev, strrpos, strtolower, strtoupper, strtr, substr, trim, unpack, utf8_decode;
|
||||
use function array_keys, array_map, array_shift, array_values, bin2hex, class_exists, defined, extension_loaded, function_exists, htmlspecialchars, htmlspecialchars_decode, iconv, iconv_strlen, iconv_substr, implode, in_array, is_array, is_callable, is_int, is_object, is_string, key, max, mb_convert_case, mb_strlen, mb_strtolower, mb_strtoupper, mb_substr, pack, preg_last_error, preg_last_error_msg, preg_quote, preg_replace, str_contains, str_ends_with, str_repeat, str_replace, str_starts_with, strlen, strpos, strrev, strrpos, strtolower, strtoupper, strtr, substr, trim, unpack;
|
||||
use const ENT_IGNORE, ENT_NOQUOTES, ICONV_IMPL, MB_CASE_TITLE, PHP_EOL, PREG_OFFSET_CAPTURE, PREG_PATTERN_ORDER, PREG_SET_ORDER, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY, PREG_SPLIT_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL;
|
||||
|
||||
|
||||
|
|
@ -333,8 +333,8 @@ class Strings
|
|||
public static function compare(string $left, string $right, ?int $length = null): bool
|
||||
{
|
||||
if (class_exists('Normalizer', autoload: false)) {
|
||||
$left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster
|
||||
$right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster
|
||||
$left = \Normalizer::normalize($left, \Normalizer::FORM_D) ?: $left; // form NFD is faster, false on invalid UTF-8
|
||||
$right = \Normalizer::normalize($right, \Normalizer::FORM_D) ?: $right; // form NFD is faster, false on invalid UTF-8
|
||||
}
|
||||
|
||||
if ($length < 0) {
|
||||
|
|
@ -385,7 +385,7 @@ class Strings
|
|||
return match (true) {
|
||||
extension_loaded('mbstring') => (int) mb_strlen($s, 'UTF-8'),
|
||||
extension_loaded('iconv') => (int) iconv_strlen($s, 'UTF-8'),
|
||||
default => strlen(@utf8_decode($s)), // deprecated
|
||||
default => strlen((string) preg_replace('#[\x80-\xBF]#', '', $s)), // strips UTF-8 continuation bytes
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +396,7 @@ class Strings
|
|||
public static function trim(string $s, string $charlist = self::TrimCharacters): string
|
||||
{
|
||||
$charlist = preg_quote($charlist, '#');
|
||||
return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '');
|
||||
return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
7
vendor/nette/utils/src/Utils/Validators.php
vendored
7
vendor/nette/utils/src/Utils/Validators.php
vendored
|
|
@ -337,14 +337,15 @@ class Validators
|
|||
public static function isUrl(string $value): bool
|
||||
{
|
||||
$alpha = "a-z\x80-\xFF";
|
||||
$octet = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])'; // 0..255
|
||||
return (bool) preg_match(<<<XX
|
||||
(^(?n)
|
||||
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
|
||||
|$octet(\\.$octet){3} # IPv4
|
||||
|\\[[0-9a-f:]{3,39}] # IPv6
|
||||
)(:\\d{1,5})? # port
|
||||
(/\\S*)? # path
|
||||
(\\?\\S*)? # query
|
||||
|
|
@ -359,7 +360,7 @@ class Validators
|
|||
*/
|
||||
public static function isUri(string $value): bool
|
||||
{
|
||||
return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
|
||||
return (bool) preg_match('#^[a-z\d+.-]+:\S+$#Di', $value);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue