Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill

- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 07:30:37 +01:00
commit 2e24a40d68
9633 changed files with 1300799 additions and 0 deletions

646
vendor/league/uri/BaseUri.php vendored Executable file
View File

@@ -0,0 +1,646 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use Deprecated;
use JsonSerializable;
use League\Uri\Contracts\UriAccess;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\MissingFeature;
use League\Uri\Idna\Converter as IdnaConverter;
use League\Uri\IPv4\Converter as IPv4Converter;
use League\Uri\IPv6\Converter as IPv6Converter;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use Stringable;
use function array_pop;
use function array_reduce;
use function count;
use function explode;
use function implode;
use function in_array;
use function preg_match;
use function rawurldecode;
use function sort;
use function str_contains;
use function str_repeat;
use function str_replace;
use function strpos;
use function substr;
/**
* @phpstan-import-type ComponentMap from UriInterface
* @deprecated since version 7.6.0
*
* @see Modifier
* @see Uri
*/
class BaseUri implements Stringable, JsonSerializable, UriAccess
{
/** @var array<string,int> */
final protected const WHATWG_SPECIAL_SCHEMES = ['ftp' => 1, 'http' => 1, 'https' => 1, 'ws' => 1, 'wss' => 1];
/** @var array<string,int> */
final protected const DOT_SEGMENTS = ['.' => 1, '..' => 1];
protected readonly Psr7UriInterface|UriInterface|null $origin;
protected readonly ?string $nullValue;
/**
* @param UriFactoryInterface|null $uriFactory Deprecated, will be removed in the next major release
*/
final protected function __construct(
protected readonly Psr7UriInterface|UriInterface $uri,
protected readonly ?UriFactoryInterface $uriFactory
) {
$this->nullValue = $this->uri instanceof Psr7UriInterface ? '' : null;
$this->origin = $this->computeOrigin($this->uri, $this->nullValue);
}
public static function from(Stringable|string $uri, ?UriFactoryInterface $uriFactory = null): static
{
$uri = static::formatHost(static::filterUri($uri, $uriFactory));
return new static($uri, $uriFactory);
}
public function withUriFactory(UriFactoryInterface $uriFactory): static
{
return new static($this->uri, $uriFactory);
}
public function withoutUriFactory(): static
{
return new static($this->uri, null);
}
public function getUri(): Psr7UriInterface|UriInterface
{
return $this->uri;
}
public function getUriString(): string
{
return $this->uri->__toString();
}
public function jsonSerialize(): string
{
return $this->uri->__toString();
}
public function __toString(): string
{
return $this->uri->__toString();
}
public function origin(): ?self
{
return match (null) {
$this->origin => null,
default => new self($this->origin, $this->uriFactory),
};
}
/**
* Returns the Unix filesystem path.
*
* The method will return null if a scheme is present and is not the `file` scheme
*/
public function unixPath(): ?string
{
return match ($this->uri->getScheme()) {
'file', $this->nullValue => rawurldecode($this->uri->getPath()),
default => null,
};
}
/**
* Returns the Windows filesystem path.
*
* The method will return null if a scheme is present and is not the `file` scheme
*/
public function windowsPath(): ?string
{
static $regexpWindowsPath = ',^(?<root>[a-zA-Z]:),';
if (!in_array($this->uri->getScheme(), ['file', $this->nullValue], true)) {
return null;
}
$originalPath = $this->uri->getPath();
$path = $originalPath;
if ('/' === ($path[0] ?? '')) {
$path = substr($path, 1);
}
if (1 === preg_match($regexpWindowsPath, $path, $matches)) {
$root = $matches['root'];
$path = substr($path, strlen($root));
return $root.str_replace('/', '\\', rawurldecode($path));
}
$host = $this->uri->getHost();
return match ($this->nullValue) {
$host => str_replace('/', '\\', rawurldecode($originalPath)),
default => '\\\\'.$host.'\\'.str_replace('/', '\\', rawurldecode($path)),
};
}
/**
* Returns a string representation of a File URI according to RFC8089.
*
* The method will return null if the URI scheme is not the `file` scheme
*/
public function toRfc8089(): ?string
{
$path = $this->uri->getPath();
return match (true) {
'file' !== $this->uri->getScheme() => null,
in_array($this->uri->getAuthority(), ['', null, 'localhost'], true) => 'file:'.match (true) {
'' === $path,
'/' === $path[0] => $path,
default => '/'.$path,
},
default => (string) $this->uri,
};
}
/**
* Tells whether the `file` scheme base URI represents a local file.
*/
public function isLocalFile(): bool
{
return match (true) {
'file' !== $this->uri->getScheme() => false,
in_array($this->uri->getAuthority(), ['', null, 'localhost'], true) => true,
default => false,
};
}
/**
* Tells whether the URI is opaque or not.
*
* A URI is opaque if and only if it is absolute
* and does not have an authority path.
*/
public function isOpaque(): bool
{
return $this->nullValue === $this->uri->getAuthority()
&& $this->isAbsolute();
}
/**
* Tells whether two URI do not share the same origin.
*/
public function isCrossOrigin(Stringable|string $uri): bool
{
if (null === $this->origin) {
return true;
}
$uri = static::filterUri($uri);
$uriOrigin = $this->computeOrigin($uri, $uri instanceof Psr7UriInterface ? '' : null);
return match(true) {
null === $uriOrigin,
$uriOrigin->__toString() !== $this->origin->__toString() => true,
default => false,
};
}
/**
* Tells whether the URI is absolute.
*/
public function isAbsolute(): bool
{
return $this->nullValue !== $this->uri->getScheme();
}
/**
* Tells whether the URI is a network path.
*/
public function isNetworkPath(): bool
{
return $this->nullValue === $this->uri->getScheme()
&& $this->nullValue !== $this->uri->getAuthority();
}
/**
* Tells whether the URI is an absolute path.
*/
public function isAbsolutePath(): bool
{
return $this->nullValue === $this->uri->getScheme()
&& $this->nullValue === $this->uri->getAuthority()
&& '/' === ($this->uri->getPath()[0] ?? '');
}
/**
* Tells whether the URI is a relative path.
*/
public function isRelativePath(): bool
{
return $this->nullValue === $this->uri->getScheme()
&& $this->nullValue === $this->uri->getAuthority()
&& '/' !== ($this->uri->getPath()[0] ?? '');
}
/**
* Tells whether both URI refers to the same document.
*/
public function isSameDocument(Stringable|string $uri): bool
{
return self::normalizedUri($this->uri)->equals(self::normalizedUri($uri));
}
private static function normalizedUri(Stringable|string $uri): Uri
{
// Normalize the URI according to RFC3986
$uri = ($uri instanceof Uri ? $uri : Uri::new($uri))->normalize();
return $uri
//Normalization as per WHATWG URL standard
//only meaningful for WHATWG Special URI scheme protocol
->when(
condition: '' === $uri->getPath() && null !== $uri->getAuthority(),
onSuccess: fn (Uri $uri) => $uri->withPath('/'),
)
//Sorting as per WHATWG URLSearchParams class
//not included on any equivalence algorithm
->when(
condition: null !== ($query = $uri->getQuery()) && str_contains($query, '&'),
onSuccess: function (Uri $uri) use ($query) {
$pairs = explode('&', (string) $query);
sort($pairs);
return $uri->withQuery(implode('&', $pairs));
}
);
}
/**
* Tells whether the URI contains an Internationalized Domain Name (IDN).
*/
public function hasIdn(): bool
{
return IdnaConverter::isIdn($this->uri->getHost());
}
/**
* Tells whether the URI contains an IPv4 regardless if it is mapped or native.
*/
public function hasIPv4(): bool
{
return IPv4Converter::fromEnvironment()->isIpv4($this->uri->getHost());
}
/**
* Resolves a URI against a base URI using RFC3986 rules.
*
* This method MUST retain the state of the submitted URI instance, and return
* a URI instance of the same type that contains the applied modifications.
*
* This method MUST be transparent when dealing with error and exceptions.
* It MUST not alter or silence them apart from validating its own parameters.
*/
public function resolve(Stringable|string $uri): static
{
$resolved = UriString::resolve($uri, $this->uri);
return new static(match ($this->uriFactory) {
null => Uri::new($resolved),
default => $this->uriFactory->createUri($resolved),
}, $this->uriFactory);
}
/**
* Relativize a URI according to a base URI.
*
* This method MUST retain the state of the submitted URI instance, and return
* a URI instance of the same type that contains the applied modifications.
*
* This method MUST be transparent when dealing with error and exceptions.
* It MUST not alter of silence them apart from validating its own parameters.
*/
public function relativize(Stringable|string $uri): static
{
$uri = static::formatHost(static::filterUri($uri, $this->uriFactory));
if ($this->canNotBeRelativize($uri)) {
return new static($uri, $this->uriFactory);
}
$null = $uri instanceof Psr7UriInterface ? '' : null;
$uri = $uri->withScheme($null)->withPort(null)->withUserInfo($null)->withHost($null);
$targetPath = $uri->getPath();
$basePath = $this->uri->getPath();
return new static(
match (true) {
$targetPath !== $basePath => $uri->withPath(static::relativizePath($targetPath, $basePath)),
static::componentEquals('query', $uri) => $uri->withPath('')->withQuery($null),
$null === $uri->getQuery() => $uri->withPath(static::formatPathWithEmptyBaseQuery($targetPath)),
default => $uri->withPath(''),
},
$this->uriFactory
);
}
final protected function computeOrigin(Psr7UriInterface|UriInterface $uri, ?string $nullValue): Psr7UriInterface|UriInterface|null
{
if ($uri instanceof Uri) {
$origin = $uri->getOrigin();
if (null === $origin) {
return null;
}
return Uri::tryNew($origin);
}
$origin = Uri::tryNew($uri)?->getOrigin();
if (null === $origin) {
return null;
}
$components = UriString::parse($origin);
return $uri
->withFragment($nullValue)
->withQuery($nullValue)
->withPath('')
->withScheme('localhost')
->withHost((string) $components['host'])
->withPort($components['port'])
->withScheme((string) $components['scheme'])
->withUserInfo($nullValue);
}
/**
* Input URI normalization to allow Stringable and string URI.
*/
final protected static function filterUri(Stringable|string $uri, UriFactoryInterface|null $uriFactory = null): Psr7UriInterface|UriInterface
{
return match (true) {
$uri instanceof UriAccess => $uri->getUri(),
$uri instanceof Psr7UriInterface,
$uri instanceof UriInterface => $uri,
$uriFactory instanceof UriFactoryInterface => $uriFactory->createUri((string) $uri),
default => Uri::new($uri),
};
}
/**
* Tells whether the component value from both URI object equals.
*
* @pqram 'query'|'authority'|'scheme' $property
*/
final protected function componentEquals(string $property, Psr7UriInterface|UriInterface $uri): bool
{
$getComponent = function (string $property, Psr7UriInterface|UriInterface $uri): ?string {
$component = match ($property) {
'query' => $uri->getQuery(),
'authority' => $uri->getAuthority(),
default => $uri->getScheme(),
};
return match (true) {
$uri instanceof UriInterface, '' !== $component => $component,
default => null,
};
};
return $getComponent($property, $uri) === $getComponent($property, $this->uri);
}
/**
* Filter the URI object.
*/
final protected static function formatHost(Psr7UriInterface|UriInterface $uri): Psr7UriInterface|UriInterface
{
$host = $uri->getHost();
try {
$converted = IPv4Converter::fromEnvironment()->toDecimal($host);
} catch (MissingFeature) {
$converted = null;
}
if (false === filter_var($converted, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$converted = IPv6Converter::compress($host);
}
return match (true) {
null !== $converted => $uri->withHost($converted),
'' === $host,
$uri instanceof UriInterface => $uri,
default => $uri->withHost((string) Uri::fromComponents(['host' => $host])->getHost()),
};
}
/**
* Tells whether the submitted URI object can be relativized.
*/
final protected function canNotBeRelativize(Psr7UriInterface|UriInterface $uri): bool
{
return !static::componentEquals('scheme', $uri)
|| !static::componentEquals('authority', $uri)
|| static::from($uri)->isRelativePath();
}
/**
* Relatives the URI for an authority-less target URI.
*/
final protected static function relativizePath(string $path, string $basePath): string
{
$baseSegments = static::getSegments($basePath);
$targetSegments = static::getSegments($path);
$targetBasename = array_pop($targetSegments);
array_pop($baseSegments);
foreach ($baseSegments as $offset => $segment) {
if (!isset($targetSegments[$offset]) || $segment !== $targetSegments[$offset]) {
break;
}
unset($baseSegments[$offset], $targetSegments[$offset]);
}
$targetSegments[] = $targetBasename;
return static::formatPath(
str_repeat('../', count($baseSegments)).implode('/', $targetSegments),
$basePath
);
}
/**
* returns the path segments.
*
* @return string[]
*/
final protected static function getSegments(string $path): array
{
return explode('/', match (true) {
'' === $path,
'/' !== $path[0] => $path,
default => substr($path, 1),
});
}
/**
* Formatting the path to keep a valid URI.
*/
final protected static function formatPath(string $path, string $basePath): string
{
$colonPosition = strpos($path, ':');
$slashPosition = strpos($path, '/');
return match (true) {
'' === $path => match (true) {
'' === $basePath,
'/' === $basePath => $basePath,
default => './',
},
false === $colonPosition => $path,
false === $slashPosition,
$colonPosition < $slashPosition => "./$path",
default => $path,
};
}
/**
* Formatting the path to keep a resolvable URI.
*/
final protected static function formatPathWithEmptyBaseQuery(string $path): string
{
$targetSegments = static::getSegments($path);
$basename = $targetSegments[array_key_last($targetSegments)];
return '' === $basename ? './' : $basename;
}
/**
* Normalizes a URI for comparison; this URI string representation is not suitable for usage as per RFC guidelines.
*
* @deprecated since version 7.6.0
*
* @codeCoverageIgnore
*/
#[Deprecated(message:'no longer used by the isSameDocument method', since:'league/uri-interfaces:7.6.0')]
final protected function normalize(Psr7UriInterface|UriInterface $uri): string
{
$newUri = $uri->withScheme($uri instanceof Psr7UriInterface ? '' : null);
if ('' === $newUri->__toString()) {
return '';
}
return UriString::normalize($newUri);
}
/**
* Remove dot segments from the URI path as per RFC specification.
*
* @deprecated since version 7.6.0
*
* @codeCoverageIgnore
*/
#[Deprecated(message:'no longer used by the isSameDocument method', since:'league/uri-interfaces:7.6.0')]
final protected function removeDotSegments(string $path): string
{
if (!str_contains($path, '.')) {
return $path;
}
$reducer = function (array $carry, string $segment): array {
if ('..' === $segment) {
array_pop($carry);
return $carry;
}
if (!isset(static::DOT_SEGMENTS[$segment])) {
$carry[] = $segment;
}
return $carry;
};
$oldSegments = explode('/', $path);
$newPath = implode('/', array_reduce($oldSegments, $reducer(...), []));
if (isset(static::DOT_SEGMENTS[$oldSegments[array_key_last($oldSegments)]])) {
$newPath .= '/';
}
// @codeCoverageIgnoreStart
// added because some PSR-7 implementations do not respect RFC3986
if (str_starts_with($path, '/') && !str_starts_with($newPath, '/')) {
return '/'.$newPath;
}
// @codeCoverageIgnoreEnd
return $newPath;
}
/**
* Resolves an URI path and query component.
*
* @return array{0:string, 1:string|null}
*
* @deprecated since version 7.6.0
*
* @codeCoverageIgnore
*/
#[Deprecated(message:'no longer used by the isSameDocument method', since:'league/uri-interfaces:7.6.0')]
final protected function resolvePathAndQuery(Psr7UriInterface|UriInterface $uri): array
{
$targetPath = $uri->getPath();
$null = $uri instanceof Psr7UriInterface ? '' : null;
if (str_starts_with($targetPath, '/')) {
return [$targetPath, $uri->getQuery()];
}
if ('' === $targetPath) {
$targetQuery = $uri->getQuery();
if ($null === $targetQuery) {
$targetQuery = $this->uri->getQuery();
}
$targetPath = $this->uri->getPath();
//@codeCoverageIgnoreStart
//because some PSR-7 Uri implementations allow this RFC3986 forbidden construction
if (null !== $this->uri->getAuthority() && !str_starts_with($targetPath, '/')) {
$targetPath = '/'.$targetPath;
}
//@codeCoverageIgnoreEnd
return [$targetPath, $targetQuery];
}
$basePath = $this->uri->getPath();
if (null !== $this->uri->getAuthority() && '' === $basePath) {
$targetPath = '/'.$targetPath;
}
if ('' !== $basePath) {
$segments = explode('/', $basePath);
array_pop($segments);
if ([] !== $segments) {
$targetPath = implode('/', $segments).'/'.$targetPath;
}
}
return [$targetPath, $uri->getQuery()];
}
}

358
vendor/league/uri/Builder.php vendored Executable file
View File

@@ -0,0 +1,358 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use BackedEnum;
use League\Uri\Contracts\Conditionable;
use League\Uri\Contracts\FragmentDirective;
use League\Uri\Contracts\Transformable;
use League\Uri\Contracts\UriComponentInterface;
use League\Uri\Exceptions\SyntaxError;
use SensitiveParameter;
use Stringable;
use Throwable;
use TypeError;
use Uri\Rfc3986\Uri as Rfc3986Uri;
use Uri\WhatWg\Url as WhatWgUrl;
use function is_bool;
use function str_replace;
use function strpos;
final class Builder implements Conditionable, Transformable
{
private ?string $scheme = null;
private ?string $username = null;
private ?string $password = null;
private ?string $host = null;
private ?int $port = null;
private ?string $path = null;
private ?string $query = null;
private ?string $fragment = null;
public function __construct(
BackedEnum|Stringable|string|null $scheme = null,
BackedEnum|Stringable|string|null $username = null,
#[SensitiveParameter] BackedEnum|Stringable|string|null $password = null,
BackedEnum|Stringable|string|null $host = null,
BackedEnum|int|null $port = null,
BackedEnum|Stringable|string|null $path = null,
BackedEnum|Stringable|string|null $query = null,
BackedEnum|Stringable|string|null $fragment = null,
) {
$this
->scheme($scheme)
->userInfo($username, $password)
->host($host)
->port($port)
->path($path)
->query($query)
->fragment($fragment);
}
/**
* @throws SyntaxError
*/
public function scheme(BackedEnum|Stringable|string|null $scheme): self
{
$scheme = $this->filterString($scheme);
if ($scheme !== $this->scheme) {
UriString::isValidScheme($scheme) || throw new SyntaxError('The scheme `'.$scheme.'` is invalid.');
$this->scheme = $scheme;
}
return $this;
}
/**
* @throws SyntaxError
*/
public function userInfo(
BackedEnum|Stringable|string|null $user,
#[SensitiveParameter] BackedEnum|Stringable|string|null $password = null
): static {
$username = Encoder::encodeUser($this->filterString($user));
$password = Encoder::encodePassword($this->filterString($password));
if ($username !== $this->username || $password !== $this->password) {
$this->username = $username;
$this->password = $password;
}
return $this;
}
/**
* @throws SyntaxError
*/
public function host(BackedEnum|Stringable|string|null $host): self
{
$host = $this->filterString($host);
if ($host !== $this->host) {
null === $host
|| HostRecord::isValid($host)
|| throw new SyntaxError('The host `'.$host.'` is invalid.');
$this->host = $host;
}
return $this;
}
/**
* @throws SyntaxError
* @throws TypeError
*/
public function port(BackedEnum|int|null $port): self
{
if ($port instanceof BackedEnum) {
1 === preg_match('/^\d+$/', (string) $port->value)
|| throw new TypeError('The port must be a valid BackedEnum containing a number.');
$port = (int) $port->value;
}
if ($port !== $this->port) {
null === $port
|| ($port >= 0 && $port < 65535)
|| throw new SyntaxError('The port value must be null or an integer between 0 and 65535.');
$this->port = $port;
}
return $this;
}
/**
* @throws SyntaxError
*/
public function authority(BackedEnum|Stringable|string|null $authority): self
{
['user' => $user, 'pass' => $pass, 'host' => $host, 'port' => $port] = UriString::parseAuthority($authority);
return $this
->userInfo($user, $pass)
->host($host)
->port($port);
}
/**
* @throws SyntaxError
*/
public function path(BackedEnum|Stringable|string|null $path): self
{
$path = $this->filterString($path);
if ($path !== $this->path) {
$this->path = null !== $path ? Encoder::encodePath($path) : null;
}
return $this;
}
/**
* @throws SyntaxError
*/
public function query(BackedEnum|Stringable|string|null $query): self
{
$query = $this->filterString($query);
if ($query !== $this->query) {
$this->query = Encoder::encodeQueryOrFragment($query);
}
return $this;
}
/**
* @throws SyntaxError
*/
public function fragment(BackedEnum|Stringable|string|null $fragment): self
{
$fragment = $this->filterString($fragment);
if ($fragment !== $this->fragment) {
$this->fragment = Encoder::encodeQueryOrFragment($fragment);
}
return $this;
}
/**
* Puts back the Builder in a freshly created state.
*/
public function reset(): self
{
$this->scheme = null;
$this->username = null;
$this->password = null;
$this->host = null;
$this->port = null;
$this->path = null;
$this->query = null;
$this->fragment = null;
return $this;
}
/**
* Executes the given callback with the current instance
* and returns the current instance.
*
* @param callable(self): self $callback
*/
public function transform(callable $callback): static
{
return $callback($this);
}
public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static
{
if (!is_bool($condition)) {
$condition = $condition($this);
}
return match (true) {
$condition => $onSuccess($this),
null !== $onFail => $onFail($this),
default => $this,
} ?? $this;
}
/**
* @throws SyntaxError if the URI can not be build with the current Builder state
*/
public function guard(Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUri = null): self
{
try {
$this->build($baseUri);
return $this;
} catch (Throwable $exception) {
throw new SyntaxError('The current builder cannot generate a valid URI.', previous: $exception);
}
}
/**
* Tells whether the URI can be built with the current Builder state.
*/
public function validate(Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUri = null): bool
{
try {
$this->build($baseUri);
return true;
} catch (Throwable) {
return false;
}
}
public function build(Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUri = null): Uri
{
$authority = $this->buildAuthority();
$path = $this->buildPath($authority);
$uriString = UriString::buildUri(
$this->scheme,
$authority,
$path,
Encoder::encodeQueryOrFragment($this->query),
Encoder::encodeQueryOrFragment($this->fragment)
);
return Uri::new(null === $baseUri ? $uriString : UriString::resolve($uriString, match (true) {
$baseUri instanceof Rfc3986Uri => $baseUri->toString(),
$baseUri instanceof WhatWgUrl => $baseUri->toAsciiString(),
default => $baseUri,
}));
}
/**
* @throws SyntaxError
*/
private function buildAuthority(): ?string
{
if (null === $this->host) {
(null === $this->username && null === $this->password && null === $this->port)
|| throw new SyntaxError('The User Information and/or the Port component(s) are set without a Host component being present.');
return null;
}
$authority = $this->host;
if (null !== $this->username || null !== $this->password) {
$userInfo = Encoder::encodeUser($this->username);
if (null !== $this->password) {
$userInfo .= ':'.Encoder::encodePassword($this->password);
}
$authority = $userInfo.'@'.$authority;
}
if (null !== $this->port) {
return $authority.':'.$this->port;
}
return $authority;
}
/**
* @throws SyntaxError
*/
private function buildPath(?string $authority): ?string
{
if (null === $this->path || '' === $this->path) {
return $this->path;
}
$path = Encoder::encodePath($this->path);
if (null !== $authority) {
return str_starts_with($path, '/') ? $path : '/'.$path;
}
if (str_starts_with($path, '//')) {
return '/.'.$path;
}
$colonPos = strpos($path, ':');
if (false !== $colonPos && null === $this->scheme) {
$slashPos = strpos($path, '/');
(false !== $slashPos && $colonPos > $slashPos) || throw new SyntaxError('In absence of the scheme and authority components, the first path segment cannot contain a colon (":") character.');
}
return $path;
}
/**
* Filter a string.
*
* @throws SyntaxError if the submitted data cannot be converted to string
*/
private function filterString(BackedEnum|Stringable|string|null $str): ?string
{
$str = match (true) {
$str instanceof FragmentDirective => $str->toFragmentValue(),
$str instanceof UriComponentInterface => $str->value(),
$str instanceof BackedEnum => (string) $str->value,
null === $str => null,
default => (string) $str,
};
if (null === $str) {
return null;
}
$str = str_replace(' ', '%20', $str);
return UriString::containsRfc3987Chars($str)
? $str
: throw new SyntaxError('The component value `'.$str.'` contains invalid characters.');
}
}

386
vendor/league/uri/Http.php vendored Executable file
View File

@@ -0,0 +1,386 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use Deprecated;
use JsonSerializable;
use League\Uri\Contracts\Conditionable;
use League\Uri\Contracts\Transformable;
use League\Uri\Contracts\UriException;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\UriTemplate\TemplateCanNotBeExpanded;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use Stringable;
use Uri\Rfc3986\Uri as Rfc3986Uri;
use Uri\WhatWg\Url as WhatWgUrl;
use function is_bool;
use function ltrim;
/**
* @phpstan-import-type InputComponentMap from UriString
*/
final class Http implements Stringable, Psr7UriInterface, JsonSerializable, Conditionable, Transformable
{
private readonly UriInterface $uri;
private function __construct(UriInterface $uri)
{
if (null === $uri->getScheme() && '' === $uri->getHost()) {
throw new SyntaxError('An URI without scheme cannot contain an empty host string according to PSR-7: '.$uri);
}
$port = $uri->getPort();
if (null !== $port && ($port < 0 || $port > 65535)) {
throw new SyntaxError('The URI port is outside the established TCP and UDP port ranges: '.$uri);
}
$this->uri = $this->normalizePsr7Uri($uri);
}
/**
* PSR-7 UriInterface makes the following normalization.
*
* Safely stringify input when possible for League UriInterface compatibility.
*
* Query, Fragment and User Info when undefined are normalized to the empty string
*/
private function normalizePsr7Uri(UriInterface $uri): UriInterface
{
$components = [];
if ('' === $uri->getFragment()) {
$components['fragment'] = null;
}
if ('' === $uri->getQuery()) {
$components['query'] = null;
}
if ('' === $uri->getUserInfo()) {
$components['user'] = null;
$components['pass'] = null;
}
return match ($components) {
[] => $uri,
default => Uri::fromComponents([...$uri->toComponents(), ...$components]),
};
}
/**
* Create a new instance from a string or a stringable object.
*/
public static function new(Rfc3986Uri|WhatwgUrl|Stringable|string $uri = ''): self
{
return new self(Uri::new($uri));
}
/**
* Create a new instance from a string or a stringable structure or returns null on failure.
*/
public static function tryNew(Rfc3986Uri|WhatwgUrl|Stringable|string $uri = ''): ?self
{
try {
return self::new($uri);
} catch (UriException) {
return null;
}
}
/**
* Create a new instance from a hash of parse_url parts.
*
* @param InputComponentMap $components a hash representation of the URI similar
* to PHP parse_url function result
*/
public static function fromComponents(array $components): self
{
$components += [
'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
'port' => null, 'path' => '', 'query' => null, 'fragment' => null,
];
if ('' === $components['user']) {
$components['user'] = null;
}
if ('' === $components['pass']) {
$components['pass'] = null;
}
if ('' === $components['query']) {
$components['query'] = null;
}
if ('' === $components['fragment']) {
$components['fragment'] = null;
}
return new self(Uri::fromComponents($components));
}
/**
* Create a new instance from the environment.
*/
public static function fromServer(array $server): self
{
return new self(Uri::fromServer($server));
}
/**
* Creates a new instance from a template.
*
* @throws TemplateCanNotBeExpanded if the variables are invalid or missing
* @throws UriException if the variables are invalid or missing
*/
public static function fromTemplate(Stringable|string $template, iterable $variables = []): self
{
return new self(Uri::fromTemplate($template, $variables));
}
/**
* Returns a new instance from a URI and a Base URI.or null on failure.
*
* The returned URI must be absolute if a base URI is provided
*/
public static function parse(WhatWgUrl|Rfc3986Uri|Stringable|string $uri, WhatWgUrl|Rfc3986Uri|Stringable|string|null $baseUri = null): ?self
{
return null !== ($uri = Uri::parse($uri, $baseUri)) ? new self($uri) : null;
}
public function getScheme(): string
{
return $this->uri->getScheme() ?? '';
}
public function getAuthority(): string
{
return $this->uri->getAuthority() ?? '';
}
public function getUserInfo(): string
{
return $this->uri->getUserInfo() ?? '';
}
public function getHost(): string
{
return $this->uri->getHost() ?? '';
}
public function getPort(): ?int
{
return $this->uri->getPort();
}
public function getPath(): string
{
$path = $this->uri->getPath();
return match (true) {
str_starts_with($path, '//') => '/'.ltrim($path, '/'),
default => $path,
};
}
public function getQuery(): string
{
return $this->uri->getQuery() ?? '';
}
public function getFragment(): string
{
return $this->uri->getFragment() ?? '';
}
public function __toString(): string
{
return $this->uri->toString();
}
public function jsonSerialize(): string
{
return $this->uri->toString();
}
/**
* Safely stringify input when possible for League UriInterface compatibility.
*/
private function filterInput(string $str): ?string
{
return match ('') {
$str => null,
default => $str,
};
}
private function newInstance(UriInterface $uri): self
{
return match ($this->uri->toString()) {
$uri->toString() => $this,
default => new self($uri),
};
}
public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static
{
if (!is_bool($condition)) {
$condition = $condition($this);
}
return match (true) {
$condition => $onSuccess($this),
null !== $onFail => $onFail($this),
default => $this,
} ?? $this;
}
public function transform(callable $callback): static
{
return $callback($this);
}
public function withScheme(string $scheme): self
{
return $this->newInstance($this->uri->withScheme($this->filterInput($scheme)));
}
public function withUserInfo(string $user, ?string $password = null): self
{
return $this->newInstance($this->uri->withUserInfo($this->filterInput($user), $password));
}
public function withHost(string $host): self
{
return $this->newInstance($this->uri->withHost($this->filterInput($host)));
}
public function withPort(?int $port): self
{
return $this->newInstance($this->uri->withPort($port));
}
public function withPath(string $path): self
{
return $this->newInstance($this->uri->withPath($path));
}
public function withQuery(string $query): self
{
return $this->newInstance($this->uri->withQuery($this->filterInput($query)));
}
public function withFragment(string $fragment): self
{
return $this->newInstance($this->uri->withFragment($this->filterInput($fragment)));
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.6.0
* @codeCoverageIgnore
* @see Http::parse()
*
* Create a new instance from a URI and a Base URI.
*
* The returned URI must be absolute.
*/
#[Deprecated(message:'use League\Uri\Http::parse() instead', since:'league/uri:7.6.0')]
public static function fromBaseUri(Rfc3986Uri|WhatwgUrl|Stringable|string $uri, Rfc3986Uri|WhatwgUrl|Stringable|string|null $baseUri = null): self
{
return new self(Uri::fromBaseUri($uri, $baseUri));
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.0.0
* @codeCoverageIgnore
* @see Http::new()
*
* Create a new instance from a string.
*/
#[Deprecated(message:'use League\Uri\Http::new() instead', since:'league/uri:7.0.0')]
public static function createFromString(Stringable|string $uri = ''): self
{
return self::new($uri);
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.0.0
* @codeCoverageIgnore
* @see Http::fromComponents()
*
* Create a new instance from a hash of parse_url parts.
*
* @param InputComponentMap $components a hash representation of the URI similar
* to PHP parse_url function result
*/
#[Deprecated(message:'use League\Uri\Http::fromComponents() instead', since:'league/uri:7.0.0')]
public static function createFromComponents(array $components): self
{
return self::fromComponents($components);
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.0.0
* @codeCoverageIgnore
* @see Http::fromServer()
*
* Create a new instance from the environment.
*/
#[Deprecated(message:'use League\Uri\Http::fromServer() instead', since:'league/uri:7.0.0')]
public static function createFromServer(array $server): self
{
return self::fromServer($server);
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.0.0
* @codeCoverageIgnore
* @see Http::new()
*
* Create a new instance from a URI object.
*/
#[Deprecated(message:'use League\Uri\Http::new() instead', since:'league/uri:7.0.0')]
public static function createFromUri(Psr7UriInterface|UriInterface $uri): self
{
return self::new($uri);
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.0.0
* @codeCoverageIgnore
* @see Http::fromBaseUri()
*
* Create a new instance from a URI and a Base URI.
*
* The returned URI must be absolute.
*/
#[Deprecated(message:'use League\Uri\Http::fromBaseUri() instead', since:'league/uri:7.0.0')]
public static function createFromBaseUri(Stringable|string $uri, Stringable|string|null $baseUri = null): self
{
return self::fromBaseUri($uri, $baseUri);
}
}

25
vendor/league/uri/HttpFactory.php vendored Executable file
View File

@@ -0,0 +1,25 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
final class HttpFactory implements UriFactoryInterface
{
public function createUri(string $uri = ''): UriInterface
{
return Http::new($uri);
}
}

20
vendor/league/uri/LICENSE vendored Executable file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 ignace nyamagana butera
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

36
vendor/league/uri/SchemeType.php vendored Executable file
View File

@@ -0,0 +1,36 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
enum SchemeType
{
case Opaque;
case Hierarchical;
case Unknown;
public function isOpaque(): bool
{
return self::Opaque === $this;
}
public function isHierarchical(): bool
{
return self::Hierarchical === $this;
}
public function isUnknown(): bool
{
return self::Unknown === $this;
}
}

1967
vendor/league/uri/Uri.php vendored Executable file

File diff suppressed because it is too large Load Diff

105
vendor/league/uri/UriInfo.php vendored Executable file
View File

@@ -0,0 +1,105 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use Deprecated;
use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
/**
* @deprecated since version 7.0.0
* @codeCoverageIgnore
* @see BaseUri
*/
final class UriInfo
{
/**
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* Tells whether the URI represents an absolute URI.
*/
#[Deprecated(message:'use League\Uri\BaseUri::isAbsolute() instead', since:'league/uri:7.0.0')]
public static function isAbsolute(Psr7UriInterface|UriInterface $uri): bool
{
return BaseUri::from($uri)->isAbsolute();
}
/**
* Tell whether the URI represents a network path.
*/
#[Deprecated(message:'use League\Uri\BaseUri::isNetworkPath() instead', since:'league/uri:7.0.0')]
public static function isNetworkPath(Psr7UriInterface|UriInterface $uri): bool
{
return BaseUri::from($uri)->isNetworkPath();
}
/**
* Tells whether the URI represents an absolute path.
*/
#[Deprecated(message:'use League\Uri\BaseUri::isAbsolutePath() instead', since:'league/uri:7.0.0')]
public static function isAbsolutePath(Psr7UriInterface|UriInterface $uri): bool
{
return BaseUri::from($uri)->isAbsolutePath();
}
/**
* Tell whether the URI represents a relative path.
*
*/
#[Deprecated(message:'use League\Uri\BaseUri::isRelativePath() instead', since:'league/uri:7.0.0')]
public static function isRelativePath(Psr7UriInterface|UriInterface $uri): bool
{
return BaseUri::from($uri)->isRelativePath();
}
/**
* Tells whether both URI refers to the same document.
*/
#[Deprecated(message:'use League\Uri\BaseUri::isSameDocument() instead', since:'league/uri:7.0.0')]
public static function isSameDocument(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $baseUri): bool
{
return BaseUri::from($baseUri)->isSameDocument($uri);
}
/**
* Returns the URI origin property as defined by WHATWG URL living standard.
*
* {@see https://url.spec.whatwg.org/#origin}
*
* For URI without a special scheme the method returns null
* For URI with the file scheme the method will return null (as this is left to the implementation decision)
* For URI with a special scheme the method returns the scheme followed by its authority (without the userinfo part)
*/
#[Deprecated(message:'use League\Uri\BaseUri::origin() instead', since:'league/uri:7.0.0')]
public static function getOrigin(Psr7UriInterface|UriInterface $uri): ?string
{
return BaseUri::from($uri)->origin()?->__toString();
}
/**
* Tells whether two URI do not share the same origin.
*
* @see UriInfo::getOrigin()
*/
#[Deprecated(message:'use League\Uri\BaseUri::isCrossOrigin() instead', since:'league/uri:7.0.0')]
public static function isCrossOrigin(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $baseUri): bool
{
return BaseUri::from($baseUri)->isCrossOrigin($uri);
}
}

56
vendor/league/uri/UriResolver.php vendored Executable file
View File

@@ -0,0 +1,56 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use Deprecated;
use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
/**
* @deprecated since version 7.0.0
* @codeCoverageIgnore
* @see BaseUri
*/
final class UriResolver
{
/**
* Resolves a URI against a base URI using RFC3986 rules.
*
* This method MUST retain the state of the submitted URI instance, and return
* a URI instance of the same type that contains the applied modifications.
*
* This method MUST be transparent when dealing with error and exceptions.
* It MUST not alter or silence them apart from validating its own parameters.
*/
#[Deprecated(message:'use League\Uri\BaseUri::resolve() instead', since:'league/uri:7.0.0')]
public static function resolve(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $baseUri): Psr7UriInterface|UriInterface
{
return BaseUri::from($baseUri)->resolve($uri)->getUri();
}
/**
* Relativizes a URI according to a base URI.
*
* This method MUST retain the state of the submitted URI instance, and return
* a URI instance of the same type that contains the applied modifications.
*
* This method MUST be transparent when dealing with error and exceptions.
* It MUST not alter or silence them apart from validating its own parameters.
*/
#[Deprecated(message:'use League\Uri\BaseUri::relativize() instead', since:'league/uri:7.0.0')]
public static function relativize(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $baseUri): Psr7UriInterface|UriInterface
{
return BaseUri::from($baseUri)->relativize($uri)->getUri();
}
}

199
vendor/league/uri/UriScheme.php vendored Executable file
View File

@@ -0,0 +1,199 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use ValueError;
/*
* Supported schemes and corresponding default port.
*
* @see https://github.com/python-hyper/hyperlink/blob/master/src/hyperlink/_url.py for the curating list definition
* @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
* @see https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
*/
enum UriScheme: string
{
case About = 'about';
case Acap = 'acap';
case Bitcoin = 'bitcoin';
case Geo = 'geo';
case Blob = 'blob';
case Afp = 'afp';
case Data = 'data';
case Dict = 'dict';
case Dns = 'dns';
case File = 'file';
case Ftp = 'ftp';
case Git = 'git';
case Gopher = 'gopher';
case Http = 'http';
case Https = 'https';
case Imap = 'imap';
case Imaps = 'imaps';
case Ipp = 'ipp';
case Ipps = 'ipps';
case Irc = 'irc';
case Ircs = 'ircs';
case Javascript = 'javascript';
case Ldap = 'ldap';
case Ldaps = 'ldaps';
case Magnet = 'magnet';
case Mailto = 'mailto';
case Mms = 'mms';
case Msrp = 'msrp';
case Msrps = 'msrps';
case Mtqp = 'mtqp';
case News = 'news';
case Nfs = 'nfs';
case Nntp = 'nntp';
case Nntps = 'nntps';
case Pkcs11 = 'pkcs11';
case Pop = 'pop';
case Prospero = 'prospero';
case Redis = 'redis';
case Rsync = 'rsync';
case Rtsp = 'rtsp';
case Rtsps = 'rtsps';
case Rtspu = 'rtspu';
case Sftp = 'sftp';
case Wss = 'wss';
case Ws = 'ws';
case Sip = 'sip';
case Sips = 'sips';
case Smb = 'smb';
case Smtp = 'smtp';
case Snmp = 'snmp';
case Ssh = 'ssh';
case Steam = 'steam';
case Svn = 'svn';
case Tel = 'tel';
case Telnet = 'telnet';
case Tn3270 = 'tn3270';
case Urn = 'urn';
case Ventrilo = 'ventrilo';
case Vnc = 'vnc';
case Wais = 'wais';
case Xmpp = 'xmpp';
public function port(): ?int
{
return match ($this) {
self::Acap => 674,
self::Afp => 548,
self::Dict => 2628,
self::Dns => 53,
self::Ftp => 21,
self::Http, self::Ws => 80,
self::Https, self::Wss => 443,
self::Git => 9418,
self::Gopher => 70,
self::Imap => 143,
self::Imaps => 993,
self::Ipp, self::Ipps => 631,
self::Irc => 194,
self::Ircs => 6697,
self::Ldap => 389,
self::Ldaps => 636,
self::Mms => 1755,
self::Msrp, self::Msrps => 2855,
self::Mtqp => 1038,
self::Nfs => 111,
self::Nntp => 119,
self::Nntps => 563,
self::Pop => 110,
self::Prospero => 1525,
self::Redis => 6379,
self::Rsync => 873,
self::Rtsp => 554,
self::Rtsps => 322,
self::Rtspu => 5005,
self::Sftp, self::Ssh => 22,
self::Smb => 445,
self::Smtp => 25,
self::Snmp => 161,
self::Svn => 3690,
self::Telnet, self::Tn3270 => 23,
self::Ventrilo => 3784,
self::Vnc => 5900,
self::Wais => 210,
self::Xmpp => 80,
default => null,
};
}
public function type(): SchemeType
{
return match ($this) {
self::Urn,
self::About,
self::Bitcoin,
self::Blob,
self::Data,
self::Geo,
self::Javascript,
self::Magnet,
self::Mailto,
self::Pkcs11,
self::Sip,
self::Sips,
self::Tel => SchemeType::Opaque,
self::File => SchemeType::Hierarchical,
self::News => SchemeType::Unknown,
default => match (true) {
null !== $this->port() => SchemeType::Hierarchical,
default => SchemeType::Unknown,
},
};
}
public function isWhatWgSpecial(): bool
{
return match ($this) {
self::Ftp,
self::Http,
self::Https,
self::Ws,
self::Wss => true,
default => false,
};
}
/**
* @return list<self>
*/
public static function fromPort(?int $port): array
{
null === $port || 0 <= $port || throw new ValueError('The submitted port cannot be negative.');
static $reverse = [];
if ([] === $reverse) {
foreach (self::cases() as $case) {
$defaultPort = $case->port();
if (null === $defaultPort) {
continue;
}
$reverse[$defaultPort] ??= [];
$reverse[$defaultPort][] = $case;
}
}
return $reverse[$port] ?? [];
}
public function builder(): Builder
{
return new Builder(scheme: $this);
}
}

292
vendor/league/uri/UriTemplate.php vendored Executable file
View File

@@ -0,0 +1,292 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use BackedEnum;
use Deprecated;
use League\Uri\Contracts\UriException;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\MissingFeature;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\UriTemplate\Template;
use League\Uri\UriTemplate\TemplateCanNotBeExpanded;
use League\Uri\UriTemplate\VariableBag;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use Stringable;
use Uri\InvalidUriException;
use Uri\Rfc3986\Uri as Rfc3986Uri;
use Uri\WhatWg\InvalidUrlException;
use Uri\WhatWg\Url as WhatWgUrl;
use function array_fill_keys;
use function array_key_exists;
use function class_exists;
/**
* Defines the URI Template syntax and the process for expanding a URI Template into a URI reference.
*
* @link https://tools.ietf.org/html/rfc6570
* @package League\Uri
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @since 6.1.0
*
* @phpstan-import-type InputValue from VariableBag
*/
final class UriTemplate implements Stringable
{
private readonly Template $template;
private readonly VariableBag $defaultVariables;
/**
* @throws SyntaxError if the template syntax is invalid
* @throws TemplateCanNotBeExpanded if the template or the variables are invalid
*/
public function __construct(BackedEnum|Stringable|string $template, iterable $defaultVariables = [])
{
$this->template = $template instanceof Template ? $template : Template::new($template);
$this->defaultVariables = $this->filterVariables($defaultVariables);
}
private function filterVariables(iterable $variables): VariableBag
{
if (!$variables instanceof VariableBag) {
$variables = new VariableBag($variables);
}
return $variables
->filter(fn ($value, string|int $name) => array_key_exists(
$name,
array_fill_keys($this->template->variableNames, 1)
));
}
/**
* Returns the string representation of the UriTemplate.
*/
public function __toString(): string
{
return $this->template->value;
}
/**
* Returns the distinct variables placeholders used in the template.
*
* @return array<string>
*/
public function getVariableNames(): array
{
return $this->template->variableNames;
}
/**
* @return array<string, InputValue>
*/
public function getDefaultVariables(): array
{
return iterator_to_array($this->defaultVariables);
}
/**
* Returns a new instance with the updated default variables.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified default variables.
*
* If present, variables whose name is not part of the current template
* possible variable names are removed.
*
* @throws TemplateCanNotBeExpanded if the variables are invalid
*/
public function withDefaultVariables(iterable $defaultVariables): self
{
$defaultVariables = $this->filterVariables($defaultVariables);
if ($this->defaultVariables->equals($defaultVariables)) {
return $this;
}
return new self($this->template, $defaultVariables);
}
private function templateExpanded(iterable $variables = []): string
{
return $this->template->expand($this->filterVariables($variables)->replace($this->defaultVariables));
}
private function templateExpandedOrFail(iterable $variables = []): string
{
return $this->template->expandOrFail($this->filterVariables($variables)->replace($this->defaultVariables));
}
/**
* @throws TemplateCanNotBeExpanded if the variables are invalid
* @throws UriException if the resulting expansion cannot be converted to a UriInterface instance
*/
public function expand(iterable $variables = [], Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUri = null): UriInterface
{
$expanded = $this->templateExpanded($variables);
return null === $baseUri ? Uri::new($expanded) : (Uri::parse($expanded, $baseUri) ?? throw new SyntaxError('Unable to expand URI'));
}
/**
* @throws MissingFeature if no Uri\Rfc3986\Uri class is found
* @throws TemplateCanNotBeExpanded if the variables are invalid
* @throws InvalidUriException if the base URI cannot be converted to a Uri\Rfc3986\Uri instance
* @throws InvalidUriException if the resulting expansion cannot be converted to a Uri\Rfc3986\Uri instance
*/
public function expandToUri(iterable $variables = [], Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUri = null): Rfc3986Uri
{
class_exists(Rfc3986Uri::class) || throw new MissingFeature('Support for '.Rfc3986Uri::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you own polyfill.');
return new Rfc3986Uri($this->templateExpanded($variables), $this->newRfc3986Uri($baseUri));
}
/**
* @throws MissingFeature if no Uri\Whatwg\Url class is found
* @throws TemplateCanNotBeExpanded if the variables are invalid
* @throws InvalidUrlException if the base URI cannot be converted to a Uri\Whatwg\Url instance
* @throws InvalidUrlException if the resulting expansion cannot be converted to a Uri\Whatwg\Url instance
*/
public function expandToUrl(iterable $variables = [], Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUrl = null, array|null &$errors = []): WhatWgUrl
{
class_exists(WhatWgUrl::class) || throw new MissingFeature('Support for '.WhatWgUrl::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you own polyfill.');
return new WhatWgUrl($this->templateExpanded($variables), $this->newWhatWgUrl($baseUrl), $errors);
}
/**
* @throws TemplateCanNotBeExpanded if the variables are invalid
* @throws UriException if the resulting expansion cannot be converted to a UriInterface instance
*/
public function expandToPsr7Uri(
iterable $variables = [],
Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUrl = null,
UriFactoryInterface $uriFactory = new HttpFactory()
): Psr7UriInterface {
$uriString = $this->templateExpandedOrFail($variables);
return $uriFactory->createUri(
null === $baseUrl
? $uriString
: UriString::resolve($uriString, match (true) {
$baseUrl instanceof Rfc3986Uri => $baseUrl->toRawString(),
$baseUrl instanceof WhatWgUrl => $baseUrl->toUnicodeString(),
default => $baseUrl,
})
);
}
/**
* @throws TemplateCanNotBeExpanded if the variables are invalid or missing
* @throws UriException if the resulting expansion cannot be converted to a UriInterface instance
*/
public function expandOrFail(iterable $variables = [], Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUri = null): UriInterface
{
$expanded = $this->templateExpandedOrFail($variables);
return null === $baseUri ? Uri::new($expanded) : (Uri::parse($expanded, $baseUri) ?? throw new SyntaxError('Unable to expand URI'));
}
/**
* @throws MissingFeature if no Uri\Rfc3986\Uri class is found
* @throws TemplateCanNotBeExpanded if the variables are invalid
* @throws InvalidUriException if the base URI cannot be converted to a Uri\Rfc3986\Uri instance
* @throws InvalidUriException if the resulting expansion cannot be converted to a Uri\Rfc3986\Uri instance
*/
public function expandToUriOrFail(iterable $variables = [], Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUri = null): Rfc3986Uri
{
class_exists(Rfc3986Uri::class) || throw new MissingFeature('Support for '.Rfc3986Uri::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you own polyfill.');
return new Rfc3986Uri($this->templateExpandedOrFail($variables), $this->newRfc3986Uri($baseUri));
}
/**
* @throws MissingFeature if no Uri\Whatwg\Url class is found
* @throws TemplateCanNotBeExpanded if the variables are invalid
* @throws InvalidUrlException if the base URI cannot be converted to a Uri\Whatwg\Url instance
* @throws InvalidUrlException if the resulting expansion cannot be converted to a Uri\Whatwg\Url instance
*/
public function expandToUrlOrFail(iterable $variables = [], Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUrl = null, array|null &$errors = []): WhatWgUrl
{
class_exists(WhatWgUrl::class) || throw new MissingFeature('Support for '.WhatWgUrl::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you own polyfill.');
return new WhatWgUrl($this->templateExpandedOrFail($variables), $this->newWhatWgUrl($baseUrl), $errors);
}
/**
* @throws TemplateCanNotBeExpanded if the variables are invalid
* @throws UriException if the resulting expansion cannot be converted to a UriInterface instance
*/
public function expandToPsr7UriOrFail(
iterable $variables = [],
Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $baseUrl = null,
UriFactoryInterface $uriFactory = new HttpFactory()
): Psr7UriInterface {
$uriString = $this->templateExpandedOrFail($variables);
return $uriFactory->createUri(
null === $baseUrl
? $uriString
: UriString::resolve($uriString, match (true) {
$baseUrl instanceof Rfc3986Uri => $baseUrl->toRawString(),
$baseUrl instanceof WhatWgUrl => $baseUrl->toUnicodeString(),
default => $baseUrl,
})
);
}
/**
* @throws InvalidUrlException
*/
private function newWhatWgUrl(Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $url = null): ?WhatWgUrl
{
return match (true) {
null === $url => null,
$url instanceof WhatWgUrl => $url,
$url instanceof Rfc3986Uri => new WhatWgUrl($url->toRawString()),
$url instanceof BackedEnum => new WhatWgUrl((string) $url->value),
default => new WhatWgUrl((string) $url),
};
}
/**
* @throws InvalidUriException
*/
private function newRfc3986Uri(Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string|null $uri = null): ?Rfc3986Uri
{
return match (true) {
null === $uri => null,
$uri instanceof Rfc3986Uri => $uri,
$uri instanceof WhatWgUrl => new Rfc3986Uri($uri->toAsciiString()),
$uri instanceof BackedEnum => new Rfc3986Uri((string) $uri->value),
default => new Rfc3986Uri((string) $uri),
};
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.6.0
* @codeCoverageIgnore
* @see UriTemplate::toString()
*
* Create a new instance from the environment.
*/
#[Deprecated(message:'use League\Uri\UriTemplate::__toString() instead', since:'league/uri:7.6.0')]
public function getTemplate(): string
{
return $this->__toString();
}
}

99
vendor/league/uri/UriTemplate/Expression.php vendored Executable file
View File

@@ -0,0 +1,99 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri\UriTemplate;
use Deprecated;
use League\Uri\Exceptions\SyntaxError;
use Stringable;
use function array_filter;
use function array_map;
use function array_unique;
use function explode;
use function implode;
/**
* @internal The class exposes the internal representation of an Expression and its usage
* @link https://www.rfc-editor.org/rfc/rfc6570#section-2.2
*/
final class Expression
{
/** @var array<VarSpecifier> */
private readonly array $varSpecifiers;
/** @var array<string> */
public readonly array $variableNames;
public readonly string $value;
private function __construct(public readonly Operator $operator, VarSpecifier ...$varSpecifiers)
{
$this->varSpecifiers = $varSpecifiers;
$this->variableNames = array_unique(
array_map(
static fn (VarSpecifier $varSpecifier): string => $varSpecifier->name,
$varSpecifiers
)
);
$this->value = '{'.$operator->value.implode(',', array_map(
static fn (VarSpecifier $varSpecifier): string => $varSpecifier->toString(),
$varSpecifiers
)).'}';
}
/**
* @throws SyntaxError if the expression is invalid
*/
public static function new(Stringable|string $expression): self
{
$parts = Operator::parseExpression($expression);
return new Expression($parts['operator'], ...array_map(
static fn (string $varSpec): VarSpecifier => VarSpecifier::new($varSpec),
explode(',', $parts['variables'])
));
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @throws SyntaxError if the expression is invalid
* @see Expression::new()
*
* @deprecated Since version 7.0.0
* @codeCoverageIgnore
*/
#[Deprecated(message:'use League\Uri\UriTemplate\Exppression::new() instead', since:'league/uri:7.0.0')]
public static function createFromString(Stringable|string $expression): self
{
return self::new($expression);
}
public function expand(VariableBag $variables): string
{
$expanded = implode(
$this->operator->separator(),
array_filter(
array_map(
fn (VarSpecifier $varSpecifier): string => $this->operator->expand($varSpecifier, $variables),
$this->varSpecifiers
),
static fn ($value): bool => '' !== $value
)
);
return match ('') {
$expanded => '',
default => $this->operator->first().$expanded,
};
}
}

225
vendor/league/uri/UriTemplate/Operator.php vendored Executable file
View File

@@ -0,0 +1,225 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri\UriTemplate;
use League\Uri\Encoder;
use League\Uri\Exceptions\SyntaxError;
use Stringable;
use function implode;
use function is_array;
use function preg_match;
use function rawurlencode;
use function str_contains;
use function substr;
/**
* Processing behavior according to the expression type operator.
*
* @internal The class exposes the internal representation of an Operator and its usage
*
* @link https://www.rfc-editor.org/rfc/rfc6570#section-2.2
* @link https://tools.ietf.org/html/rfc6570#appendix-A
*/
enum Operator: string
{
/**
* Expression regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc6570#section-2.2
*/
private const REGEXP_EXPRESSION = '/^\{(?:(?<operator>[\.\/;\?&\=,\!@\|\+#])?(?<variables>[^\}]*))\}$/';
/**
* Reserved Operator characters.
*
* @link https://tools.ietf.org/html/rfc6570#section-2.2
*/
private const RESERVED_OPERATOR = '=,!@|';
case None = '';
case ReservedChars = '+';
case Label = '.';
case Path = '/';
case PathParam = ';';
case Query = '?';
case QueryPair = '&';
case Fragment = '#';
public function first(): string
{
return match ($this) {
self::None, self::ReservedChars => '',
default => $this->value,
};
}
public function separator(): string
{
return match ($this) {
self::None, self::ReservedChars, self::Fragment => ',',
self::Query, self::QueryPair => '&',
default => $this->value,
};
}
public function isNamed(): bool
{
return match ($this) {
self::Query, self::PathParam, self::QueryPair => true,
default => false,
};
}
/**
* Removes percent encoding on reserved characters (used with + and # modifiers).
*/
public function decode(string $var): string
{
return match ($this) {
Operator::ReservedChars, Operator::Fragment => (string) Encoder::encodeQueryOrFragment($var),
default => rawurlencode($var),
};
}
/**
* @throws SyntaxError if the expression is invalid
* @throws SyntaxError if the operator used in the expression is invalid
* @throws SyntaxError if the contained variable specifiers are invalid
*
* @return array{operator:Operator, variables:string}
*/
public static function parseExpression(Stringable|string $expression): array
{
$expression = (string) $expression;
if (1 !== preg_match(self::REGEXP_EXPRESSION, $expression, $parts)) {
throw new SyntaxError('The expression "'.$expression.'" is invalid.');
}
/** @var array{operator:string, variables:string} $parts */
$parts = $parts + ['operator' => ''];
if ('' !== $parts['operator'] && str_contains(self::RESERVED_OPERATOR, $parts['operator'])) {
throw new SyntaxError('The operator used in the expression "'.$expression.'" is reserved.');
}
return [
'operator' => self::from($parts['operator']),
'variables' => $parts['variables'],
];
}
/**
* Replaces an expression with the given variables.
*
* @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
* @throws TemplateCanNotBeExpanded if the variables contains nested array values
*/
public function expand(VarSpecifier $varSpecifier, VariableBag $variables): string
{
$value = $variables->fetch($varSpecifier->name);
if (null === $value) {
return '';
}
[$expanded, $actualQuery] = $this->inject($value, $varSpecifier);
if (!$actualQuery) {
return $expanded;
}
if ('&' !== $this->separator() && '' === $expanded) {
return $varSpecifier->name;
}
return $varSpecifier->name.'='.$expanded;
}
/**
* @param string|array<string> $value
*
* @return array{0:string, 1:bool}
*/
private function inject(array|string $value, VarSpecifier $varSpec): array
{
if (is_array($value)) {
return $this->replaceList($value, $varSpec);
}
if (':' === $varSpec->modifier) {
$value = substr($value, 0, $varSpec->position);
}
return [$this->decode($value), $this->isNamed()];
}
/**
* Expands an expression using a list of values.
*
* @param array<string> $value
*
* @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
*
* @return array{0:string, 1:bool}
*/
private function replaceList(array $value, VarSpecifier $varSpec): array
{
if (':' === $varSpec->modifier) {
throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name);
}
if ([] === $value) {
return ['', false];
}
$pairs = [];
$isList = array_is_list($value);
$useQuery = $this->isNamed();
foreach ($value as $key => $var) {
if (!$isList) {
$key = rawurlencode((string) $key);
}
$var = $this->decode($var);
if ('*' === $varSpec->modifier) {
if (!$isList) {
$var = $key.'='.$var;
} elseif ($key > 0 && $useQuery) {
$var = $varSpec->name.'='.$var;
}
}
$pairs[$key] = $var;
}
if ('*' === $varSpec->modifier) {
if (!$isList) {
// Don't prepend the value name when using the `explode` modifier with an associative array.
$useQuery = false;
}
return [implode($this->separator(), $pairs), $useQuery];
}
if (!$isList) {
// When an associative array is encountered and the `explode` modifier is not set, then
// the result must be a comma separated list of keys followed by their respective values.
$retVal = [];
foreach ($pairs as $offset => $data) {
$retVal[$offset] = $offset.','.$data;
}
$pairs = $retVal;
}
return [implode(',', $pairs), $useQuery];
}
}

148
vendor/league/uri/UriTemplate/Template.php vendored Executable file
View File

@@ -0,0 +1,148 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri\UriTemplate;
use BackedEnum;
use Deprecated;
use League\Uri\Exceptions\SyntaxError;
use Stringable;
use function array_filter;
use function array_map;
use function array_reduce;
use function array_unique;
use function preg_match_all;
use function preg_replace;
use function str_replace;
use function strpbrk;
use const PREG_SET_ORDER;
/**
* @internal The class exposes the internal representation of a Template and its usage
*/
final class Template implements Stringable
{
/**
* Expression regular expression pattern.
*/
private const REGEXP_EXPRESSION_DETECTOR = '/(?<expression>\{[^}]*})/x';
/** @var array<Expression> */
private readonly array $expressions;
/** @var array<string> */
public readonly array $variableNames;
private function __construct(public readonly string $value, Expression ...$expressions)
{
$this->expressions = $expressions;
$this->variableNames = array_unique(
array_merge(
...array_map(
static fn (Expression $expression): array => $expression->variableNames,
$expressions
)
)
);
}
/**
* @throws SyntaxError if the template contains invalid expressions
* @throws SyntaxError if the template contains invalid variable specification
*/
public static function new(BackedEnum|Stringable|string $template): self
{
if ($template instanceof BackedEnum) {
$template = $template->value;
}
$template = (string) $template;
/** @var string $remainder */
$remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
false === strpbrk($remainder, '{}') || throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $founds, PREG_SET_ORDER);
return new self($template, ...array_values(
array_reduce($founds, function (array $carry, array $found): array {
if (!isset($carry[$found['expression']])) {
$carry[$found['expression']] = Expression::new($found['expression']);
}
return $carry;
}, [])
));
}
/**
* @throws TemplateCanNotBeExpanded if the variables are invalid
*/
public function expand(iterable $variables = []): string
{
if (!$variables instanceof VariableBag) {
$variables = new VariableBag($variables);
}
return $this->expandAll($variables);
}
/**
* @throws TemplateCanNotBeExpanded if the variables are invalid or missing
*/
public function expandOrFail(iterable $variables = []): string
{
if (!$variables instanceof VariableBag) {
$variables = new VariableBag($variables);
}
$missing = array_filter($this->variableNames, fn (string $name): bool => !isset($variables[$name]));
if ([] !== $missing) {
throw TemplateCanNotBeExpanded::dueToMissingVariables(...$missing);
}
return $this->expandAll($variables);
}
private function expandAll(VariableBag $variables): string
{
return array_reduce(
$this->expressions,
fn (string $uri, Expression $expr): string => str_replace($expr->value, $expr->expand($variables), $uri),
$this->value
);
}
public function __toString(): string
{
return $this->value;
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @throws SyntaxError if the template contains invalid expressions
* @throws SyntaxError if the template contains invalid variable specification
* @deprecated Since version 7.0.0
* @codeCoverageIgnore
* @see Template::new()
*
* Create a new instance from a string.
*
*/
#[Deprecated(message:'use League\Uri\UriTemplate\Template::new() instead', since:'league/uri:7.0.0')]
public static function createFromString(Stringable|string $template): self
{
return self::new($template);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri\UriTemplate;
use InvalidArgumentException;
use League\Uri\Contracts\UriException;
class TemplateCanNotBeExpanded extends InvalidArgumentException implements UriException
{
public readonly array $variablesNames;
public function __construct(string $message = '', string ...$variableNames)
{
parent::__construct($message, 0, null);
$this->variablesNames = $variableNames;
}
public static function dueToUnableToProcessValueListWithPrefix(string $variableName): self
{
return new self('The ":" modifier cannot be applied on "'.$variableName.'" since it is a list of values.', $variableName);
}
public static function dueToNestedListOfValue(string $variableName): self
{
return new self('The "'.$variableName.'" cannot be a nested list.', $variableName);
}
public static function dueToMissingVariables(string ...$variableNames): self
{
return new self('The following required variables are missing: `'.implode('`, `', $variableNames).'`.', ...$variableNames);
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri\UriTemplate;
use League\Uri\Exceptions\SyntaxError;
use function preg_match;
/**
* @internal The class exposes the internal representation of a Var Specifier
* @link https://www.rfc-editor.org/rfc/rfc6570#section-2.3
*/
final class VarSpecifier
{
/**
* Variables specification regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc6570#section-2.3
*/
private const REGEXP_VARSPEC = '/^(?<name>(?:[A-z0-9_\.]|%[0-9a-fA-F]{2})+)(?<modifier>\:(?<position>\d+)|\*)?$/';
private const MODIFIER_POSITION_MAX_POSITION = 10_000;
private function __construct(
public readonly string $name,
public readonly string $modifier,
public readonly int $position
) {
}
public static function new(string $specification): self
{
1 === preg_match(self::REGEXP_VARSPEC, $specification, $parsed) || throw new SyntaxError('The variable specification "'.$specification.'" is invalid.');
$properties = ['name' => $parsed['name'], 'modifier' => $parsed['modifier'] ?? '', 'position' => $parsed['position'] ?? ''];
if ('' !== $properties['position']) {
$properties['position'] = (int) $properties['position'];
$properties['modifier'] = ':';
}
if ('' === $properties['position']) {
$properties['position'] = 0;
}
if (self::MODIFIER_POSITION_MAX_POSITION <= $properties['position']) {
throw new SyntaxError('The variable specification "'.$specification.'" is invalid the position modifier must be lower than 10000.');
}
return new self($properties['name'], $properties['modifier'], $properties['position']);
}
public function toString(): string
{
return $this->name.$this->modifier.match (true) {
0 < $this->position => $this->position,
default => '',
};
}
}

162
vendor/league/uri/UriTemplate/VariableBag.php vendored Executable file
View File

@@ -0,0 +1,162 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri\UriTemplate;
use ArrayAccess;
use BackedEnum;
use Closure;
use Countable;
use IteratorAggregate;
use Stringable;
use Traversable;
use function array_filter;
use function is_bool;
use function is_scalar;
use const ARRAY_FILTER_USE_BOTH;
/**
* @internal The class exposes the internal representation of variable bags
*
* @phpstan-type InputValue string|bool|int|float|array<string|bool|int|float>
*
* @implements ArrayAccess<string, InputValue>
* @implements IteratorAggregate<string, InputValue>
*/
final class VariableBag implements ArrayAccess, Countable, IteratorAggregate
{
/**
* @var array<string,string|array<string>>
*/
private array $variables = [];
/**
* @param iterable<array-key, InputValue> $variables
*/
public function __construct(iterable $variables = [])
{
foreach ($variables as $name => $value) {
$this->assign((string) $name, $value);
}
}
public function count(): int
{
return count($this->variables);
}
public function getIterator(): Traversable
{
yield from $this->variables;
}
public function offsetExists(mixed $offset): bool
{
return array_key_exists($offset, $this->variables);
}
public function offsetUnset(mixed $offset): void
{
unset($this->variables[$offset]);
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->assign($offset, $value); /* @phpstan-ignore-line */
}
public function offsetGet(mixed $offset): mixed
{
return $this->fetch($offset);
}
/**
* Tells whether the bag is empty or not.
*/
public function isEmpty(): bool
{
return [] === $this->variables;
}
/**
* Tells whether the bag is empty or not.
*/
public function isNotEmpty(): bool
{
return [] !== $this->variables;
}
public function equals(mixed $value): bool
{
return $value instanceof self
&& $this->variables === $value->variables;
}
/**
* Fetches the variable value if none found returns null.
*
* @return null|string|array<string>
*/
public function fetch(string $name): null|string|array
{
return $this->variables[$name] ?? null;
}
/**
* @param Stringable|InputValue $value
*/
public function assign(string $name, BackedEnum|Stringable|string|bool|int|float|array|null $value): void
{
$this->variables[$name] = $this->normalizeValue($value, $name, true);
}
/**
* @param Stringable|InputValue $value
*
* @throws TemplateCanNotBeExpanded if the value contains nested list
*/
private function normalizeValue(
BackedEnum|Stringable|string|float|int|bool|array|null $value,
string $name,
bool $isNestedListAllowed
): array|string {
if ($value instanceof BackedEnum) {
$value = $value->value;
}
return match (true) {
is_bool($value) => true === $value ? '1' : '0',
(null === $value || is_scalar($value) || $value instanceof Stringable) => (string) $value,
!$isNestedListAllowed => throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name),
default => array_map(fn ($var): array|string => self::normalizeValue($var, $name, false), $value),
};
}
/**
* Replaces elements from passed variables into the current instance.
*/
public function replace(VariableBag $variables): self
{
return new self($this->variables + $variables->variables);
}
/**
* Filters elements using the closure.
*/
public function filter(Closure $fn): self
{
return new self(array_filter($this->variables, $fn, ARRAY_FILTER_USE_BOTH));
}
}

602
vendor/league/uri/Urn.php vendored Executable file
View File

@@ -0,0 +1,602 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use BackedEnum;
use Closure;
use JsonSerializable;
use League\Uri\Contracts\Conditionable;
use League\Uri\Contracts\Transformable;
use League\Uri\Contracts\UriComponentInterface;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\UriTemplate\Template;
use Stringable;
use Uri\Rfc3986\Uri as Rfc3986Uri;
use Uri\WhatWg\Url as WhatWgUrl;
use function is_bool;
use function preg_match;
use function str_replace;
use function strtolower;
/**
* @phpstan-type UrnSerialize array{0: array{urn: non-empty-string}, 1: array{}}
* @phpstan-import-type InputComponentMap from UriString
* @phpstan-type UrnMap array{
* scheme: 'urn',
* nid: string,
* nss: string,
* r_component: ?string,
* q_component: ?string,
* f_component: ?string,
* }
*/
final class Urn implements Conditionable, Stringable, JsonSerializable, Transformable
{
/**
* RFC8141 regular expression URN splitter.
*
* The regexp does not perform any look-ahead.
* Not all invalid URN are caught. Some
* post-regexp-validation checks
* are mandatory.
*
* @link https://datatracker.ietf.org/doc/html/rfc8141#section-2
*
* @var string
*/
private const REGEXP_URN_PARTS = '/^
urn:
(?<nid>[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?): # NID
(?<nss>.*?) # NSS
(?<frc>\?\+(?<rcomponent>.*?))? # r-component
(?<fqc>\?\=(?<qcomponent>.*?))? # q-component
(?:\#(?<fcomponent>.*))? # f-component
$/xi';
/**
* RFC8141 namespace identifier regular expression.
*
* @link https://datatracker.ietf.org/doc/html/rfc8141#section-2
*
* @var string
*/
private const REGEX_NID_SEQUENCE = '/^[a-z0-9]([a-z0-9-]{0,30})[a-z0-9]$/xi';
/** @var non-empty-string */
private readonly string $uriString;
/** @var non-empty-string */
private readonly string $nid;
/** @var non-empty-string */
private readonly string $nss;
/** @var non-empty-string|null */
private readonly ?string $rComponent;
/** @var non-empty-string|null */
private readonly ?string $qComponent;
/** @var non-empty-string|null */
private readonly ?string $fComponent;
/**
* @param Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string $urn the percent-encoded URN
*/
public static function parse(Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string $urn): ?Urn
{
try {
return self::fromString($urn);
} catch (SyntaxError) {
return null;
}
}
/**
* @param Rfc3986Uri|WhatWgUrl|Stringable|string $urn the percent-encoded URN
* @see self::fromString()
*
* @throws SyntaxError if the URN is invalid
*/
public static function new(Rfc3986Uri|WhatWgUrl|Stringable|string $urn): self
{
return self::fromString($urn);
}
/**
* @param Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string $urn the percent-encoded URN
*
* @throws SyntaxError if the URN is invalid
*/
public static function fromString(Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string $urn): self
{
$urn = match (true) {
$urn instanceof Rfc3986Uri => $urn->toRawString(),
$urn instanceof WhatWgUrl => $urn->toAsciiString(),
$urn instanceof BackedEnum => (string) $urn->value,
default => (string) $urn,
};
UriString::containsRfc3986Chars($urn) || throw new SyntaxError('The URN is malformed, it contains invalid characters.');
1 === preg_match(self::REGEXP_URN_PARTS, $urn, $matches) || throw new SyntaxError('The URN string is invalid.');
return new self(
nid: $matches['nid'],
nss: $matches['nss'],
rComponent: (isset($matches['frc']) && '' !== $matches['frc']) ? $matches['rcomponent'] : null,
qComponent: (isset($matches['fqc']) && '' !== $matches['fqc']) ? $matches['qcomponent'] : null,
fComponent: $matches['fcomponent'] ?? null,
);
}
/**
* Create a new instance from a hash representation of the URI similar
* to PHP parse_url function result.
*
* @param InputComponentMap $components a hash representation of the URI similar to PHP parse_url function result
*/
public static function fromComponents(array $components = []): self
{
$components += [
'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
'port' => null, 'path' => '', 'query' => null, 'fragment' => null,
];
return self::fromString(UriString::build($components));
}
/**
* @param Stringable|string $nss the percent-encoded NSS
*
* @throws SyntaxError if the URN is invalid
*/
public static function fromRfc2141(BackedEnum|Stringable|string $nid, BackedEnum|Stringable|string $nss): self
{
if ($nid instanceof BackedEnum) {
$nid = $nid->value;
}
if ($nss instanceof BackedEnum) {
$nss = $nss->value;
}
return new self((string) $nid, (string) $nss);
}
/**
* @param string $nss the percent-encoded NSS
* @param ?string $rComponent the percent-encoded r-component
* @param ?string $qComponent the percent-encoded q-component
* @param ?string $fComponent the percent-encoded f-component
*
* @throws SyntaxError if one of the URN part is invalid
*/
private function __construct(
string $nid,
string $nss,
?string $rComponent = null,
?string $qComponent = null,
?string $fComponent = null,
) {
('' !== $nid && 1 === preg_match(self::REGEX_NID_SEQUENCE, $nid)) || throw new SyntaxError('The URN is malformed, the NID is invalid.');
('' !== $nss && Encoder::isPathEncoded($nss)) || throw new SyntaxError('The URN is malformed, the NSS is invalid.');
/** @param Closure(string): ?non-empty-string $closure */
$validateComponent = static fn (?string $value, Closure $closure, string $name): ?string => match (true) {
null === $value,
('' !== $value && 1 !== preg_match('/[#?]/', $value) && $closure($value)) => $value,
default => throw new SyntaxError('The URN is malformed, the `'.$name.'` component is invalid.'),
};
$this->nid = $nid;
$this->nss = $nss;
$this->rComponent = $validateComponent($rComponent, Encoder::isPathEncoded(...), 'r-component');
$this->qComponent = $validateComponent($qComponent, Encoder::isQueryEncoded(...), 'q-component');
$this->fComponent = $validateComponent($fComponent, Encoder::isFragmentEncoded(...), 'f-component');
$this->uriString = $this->setUriString();
}
/**
* @return non-empty-string
*/
private function setUriString(): string
{
$str = $this->toRfc2141();
if (null !== $this->rComponent) {
$str .= '?+'.$this->rComponent;
}
if (null !== $this->qComponent) {
$str .= '?='.$this->qComponent;
}
if (null !== $this->fComponent) {
$str .= '#'.$this->fComponent;
}
return $str;
}
/**
* Returns the NID.
*
* @return non-empty-string
*/
public function getNid(): string
{
return $this->nid;
}
/**
* Returns the percent-encoded NSS.
*
* @return non-empty-string
*/
public function getNss(): string
{
return $this->nss;
}
/**
* Returns the percent-encoded r-component string or null if it is not set.
*
* @return ?non-empty-string
*/
public function getRComponent(): ?string
{
return $this->rComponent;
}
/**
* Returns the percent-encoded q-component string or null if it is not set.
*
* @return ?non-empty-string
*/
public function getQComponent(): ?string
{
return $this->qComponent;
}
/**
* Returns the percent-encoded f-component string or null if it is not set.
*
* @return ?non-empty-string
*/
public function getFComponent(): ?string
{
return $this->fComponent;
}
/**
* Returns the RFC8141 URN string representation.
*
* @return non-empty-string
*/
public function toString(): string
{
return $this->uriString;
}
/**
* Returns the RFC2141 URN string representation.
*
* @return non-empty-string
*/
public function toRfc2141(): string
{
return 'urn:'.$this->nid.':'.$this->nss;
}
/**
* Returns the human-readable string representation of the URN as an IRI.
*
* @see https://datatracker.ietf.org/doc/html/rfc3987
*/
public function toDisplayString(): string
{
return UriString::toIriString($this->uriString);
}
/**
* Returns the RFC8141 URN string representation.
*
* @see self::toString()
*
* @return non-empty-string
*/
public function __toString(): string
{
return $this->toString();
}
/**
* Returns the RFC8141 URN string representation.
* @see self::toString()
*
* @return non-empty-string
*/
public function jsonSerialize(): string
{
return $this->toString();
}
/**
* Returns the RFC3986 representation of the current URN.
*
* If a template URI is used the following variables as present
* {nid} for the namespace identifier
* {nss} for the namespace specific string
* {r_component} for the r-component without its delimiter
* {q_component} for the q-component without its delimiter
* {f_component} for the f-component without its delimiter
*/
public function resolve(UriTemplate|Template|BackedEnum|string|null $template = null): UriInterface
{
return null !== $template ? Uri::fromTemplate($template, $this->toComponents()) : Uri::new($this->uriString);
}
public function hasRComponent(): bool
{
return null !== $this->rComponent;
}
public function hasQComponent(): bool
{
return null !== $this->qComponent;
}
public function hasFComponent(): bool
{
return null !== $this->fComponent;
}
public function hasOptionalComponent(): bool
{
return null !== $this->rComponent
|| null !== $this->qComponent
|| null !== $this->fComponent;
}
/**
* Return an instance with the specified NID.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified NID.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withNid(BackedEnum|Stringable|string $nid): self
{
if ($nid instanceof BackedEnum) {
$nid = $nid->value;
}
$nid = (string) $nid;
return $this->nid === $nid ? $this : new self(
nid: $nid,
nss: $this->nss,
rComponent: $this->rComponent,
qComponent: $this->qComponent,
fComponent: $this->fComponent,
);
}
/**
* Return an instance with the specified NSS.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified NSS.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withNss(BackedEnum|Stringable|string $nss): self
{
$nss = Encoder::encodePath($nss);
return $this->nss === $nss ? $this : new self(
nid: $this->nid,
nss: $nss,
rComponent: $this->rComponent,
qComponent: $this->qComponent,
fComponent: $this->fComponent,
);
}
/**
* Return an instance with the specified r-component.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified r-component.
*
* The component is removed if the value is null.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withRComponent(BackedEnum|Stringable|string|null $component): self
{
if ($component instanceof BackedEnum) {
$component = (string) $component->value;
}
if ($component instanceof UriComponentInterface) {
$component = $component->value();
}
if (null !== $component) {
$component = self::formatComponent(Encoder::encodePath($component));
}
return $this->rComponent === $component ? $this : new self(
nid: $this->nid,
nss: $this->nss,
rComponent: $component,
qComponent: $this->qComponent,
fComponent: $this->fComponent,
);
}
private static function formatComponent(?string $component): ?string
{
return null === $component ? null : str_replace(['?', '#'], ['%3F', '%23'], $component);
}
/**
* Return an instance with the specified q-component.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified q-component.
*
* The component is removed if the value is null.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withQComponent(BackedEnum|Stringable|string|null $component): self
{
if ($component instanceof UriComponentInterface) {
$component = $component->value();
}
$component = self::formatComponent(Encoder::encodeQueryOrFragment($component));
return $this->qComponent === $component ? $this : new self(
nid: $this->nid,
nss: $this->nss,
rComponent: $this->rComponent,
qComponent: $component,
fComponent: $this->fComponent,
);
}
/**
* Return an instance with the specified f-component.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified f-component.
*
* The component is removed if the value is null.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withFComponent(BackedEnum|Stringable|string|null $component): self
{
if ($component instanceof UriComponentInterface) {
$component = $component->value();
}
$component = self::formatComponent(Encoder::encodeQueryOrFragment($component));
return $this->fComponent === $component ? $this : new self(
nid: $this->nid,
nss: $this->nss,
rComponent: $this->rComponent,
qComponent: $this->qComponent,
fComponent: $component,
);
}
public function normalize(): self
{
$copy = new self(
nid: strtolower($this->nid),
nss: (string) Encoder::normalizePath($this->nss),
rComponent: null === $this->rComponent ? $this->rComponent : Encoder::normalizePath($this->rComponent),
qComponent: Encoder::normalizeQuery($this->qComponent),
fComponent: Encoder::normalizeFragment($this->fComponent),
);
return $copy->uriString === $this->uriString ? $this : $copy;
}
public function equals(Urn|Rfc3986Uri|WhatWgUrl|BackedEnum|Stringable|string $other, UrnComparisonMode $urnComparisonMode = UrnComparisonMode::ExcludeComponents): bool
{
if (!$other instanceof Urn) {
$other = self::parse($other);
}
return (null !== $other) && match ($urnComparisonMode) {
UrnComparisonMode::ExcludeComponents => $other->normalize()->toRfc2141() === $this->normalize()->toRfc2141(),
UrnComparisonMode::IncludeComponents => $other->normalize()->toString() === $this->normalize()->toString(),
};
}
public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static
{
if (!is_bool($condition)) {
$condition = $condition($this);
}
return match (true) {
$condition => $onSuccess($this),
null !== $onFail => $onFail($this),
default => $this,
} ?? $this;
}
public function transform(callable $callback): static
{
return $callback($this);
}
/**
* @return UrnSerialize
*/
public function __serialize(): array
{
return [['urn' => $this->toString()], []];
}
/**
* @param UrnSerialize $data
*
* @throws SyntaxError
*/
public function __unserialize(array $data): void
{
[$properties] = $data;
$uri = self::fromString($properties['urn'] ?? throw new SyntaxError('The `urn` property is missing from the serialized object.'));
$this->nid = $uri->nid;
$this->nss = $uri->nss;
$this->rComponent = $uri->rComponent;
$this->qComponent = $uri->qComponent;
$this->fComponent = $uri->fComponent;
$this->uriString = $uri->uriString;
}
/**
* @return UrnMap
*/
public function toComponents(): array
{
return [
'scheme' => 'urn',
'nid' => $this->nid,
'nss' => $this->nss,
'r_component' => $this->rComponent,
'q_component' => $this->qComponent,
'f_component' => $this->fComponent,
];
}
/**
* @return UrnMap
*/
public function __debugInfo(): array
{
return $this->toComponents();
}
}

84
vendor/league/uri/composer.json vendored Executable file
View File

@@ -0,0 +1,84 @@
{
"name": "league/uri",
"type": "library",
"description" : "URI manipulation library",
"keywords": [
"url",
"uri",
"urn",
"uri-template",
"rfc2141",
"rfc3986",
"rfc3987",
"rfc8141",
"rfc6570",
"psr-7",
"parse_url",
"http",
"https",
"ws",
"ftp",
"data-uri",
"file-uri",
"middleware",
"parse_str",
"query-string",
"querystring",
"hostname"
],
"license": "MIT",
"homepage": "https://uri.thephpleague.com",
"authors": [
{
"name" : "Ignace Nyamagana Butera",
"email" : "nyamsprod@gmail.com",
"homepage" : "https://nyamsprod.com"
}
],
"support": {
"forum": "https://thephpleague.slack.com",
"docs": "https://uri.thephpleague.com",
"issues": "https://github.com/thephpleague/uri-src/issues"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/nyamsprod"
}
],
"require": {
"php": "^8.1",
"league/uri-interfaces": "^7.8",
"psr/http-factory": "^1"
},
"autoload": {
"psr-4": {
"League\\Uri\\": ""
}
},
"conflict": {
"league/uri-schemes": "^1.0"
},
"suggest": {
"ext-bcmath": "to improve IPV4 host parsing",
"ext-dom": "to convert the URI into an HTML anchor tag",
"ext-fileinfo": "to create Data URI from file contennts",
"ext-gmp": "to improve IPV4 host parsing",
"ext-intl": "to handle IDN host with the best performance",
"ext-uri": "to use the PHP native URI class",
"jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
"league/uri-components" : "to provide additional tools to manipulate URI objects components",
"league/uri-polyfill" : "to backport the PHP URI extension for older versions of PHP",
"php-64bit": "to improve IPV4 host parsing",
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present",
"rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification"
},
"extra": {
"branch-alias": {
"dev-master": "7.x-dev"
}
},
"config": {
"sort-packages": true
}
}