Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

View File

@ -0,0 +1,84 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\MissingFeature;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use Stringable;
interface AuthorityInterface extends UriComponentInterface
{
/**
* Returns the host component of the authority.
*/
public function getHost() : ?string;
/**
* Returns the port component of the authority.
*/
public function getPort() : ?int;
/**
* Returns the user information component of the authority.
*/
public function getUserInfo() : ?string;
/**
* Returns an associative array containing all the Authority components.
*
* The returned a hashmap similar to PHP's parse_url return value
*
* @link https://tools.ietf.org/html/rfc3986
*
* @return array{user: ?string, pass : ?string, host: ?string, port: ?int}
*/
public function components() : array;
/**
* Return an instance with the specified host.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified host.
*
* A null value provided for the host is equivalent to removing the host
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
* @throws MissingFeature for component or transformations
* requiring IDN support when IDN support is not present
* or misconfigured.
*/
public function withHost(Stringable|string|null $host) : self;
/**
* Return an instance with the specified port.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified port.
*
* A null value provided for the port is equivalent to removing the port
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withPort(?int $port) : self;
/**
* Return an instance with the specified user information.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified user information.
*
* Password is optional, but the user information MUST include the
* user; a null value for the user is equivalent to removing user
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withUserInfo(Stringable|string|null $user, Stringable|string|null $password = null) : self;
}

View File

@ -0,0 +1,84 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use SplFileObject;
use Stringable;
interface DataPathInterface extends PathInterface
{
/**
* Retrieve the data mime type associated to the URI.
*
* If no mimetype is present, this method MUST return the default mimetype 'text/plain'.
*
* @see http://tools.ietf.org/html/rfc2397#section-2
*/
public function getMimeType() : string;
/**
* Retrieve the parameters associated with the Mime Type of the URI.
*
* If no parameters is present, this method MUST return the default parameter 'charset=US-ASCII'.
*
* @see http://tools.ietf.org/html/rfc2397#section-2
*/
public function getParameters() : string;
/**
* Retrieve the mediatype associated with the URI.
*
* If no mediatype is present, this method MUST return the default parameter 'text/plain;charset=US-ASCII'.
*
* @see http://tools.ietf.org/html/rfc2397#section-3
*
* @return string The URI scheme.
*/
public function getMediaType() : string;
/**
* Retrieves the data string.
*
* Retrieves the data part of the path. If no data part is provided return
* an empty string
*/
public function getData() : string;
/**
* Tells whether the data is binary safe encoded.
*/
public function isBinaryData() : bool;
/**
* Save the data to a specific file.
*/
public function save(string $path, string $mode = 'w') : SplFileObject;
/**
* Returns an instance where the data part is base64 encoded.
*
* This method MUST retain the state of the current instance, and return
* an instance where the data part is base64 encoded
*/
public function toBinary() : self;
/**
* Returns an instance where the data part is url encoded following RFC3986 rules.
*
* This method MUST retain the state of the current instance, and return
* an instance where the data part is url encoded
*/
public function toAscii() : self;
/**
* Return an instance with the specified mediatype parameters.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified mediatype parameters.
*
* Users must provide encoded characters.
*
* An empty parameters value is equivalent to removing the parameter.
*/
public function withParameters(Stringable|string $parameters) : self;
}

View File

@ -0,0 +1,103 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use Countable;
use Iterator;
use IteratorAggregate;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use Stringable;
/**
* @extends IteratorAggregate<string>
*/
interface DomainHostInterface extends Countable, HostInterface, IteratorAggregate
{
/**
* Returns the labels total number.
*/
public function count() : int;
/**
* Iterate over the Domain labels.
*
* @return Iterator<string>
*/
public function getIterator() : Iterator;
/**
* Retrieves a single host label.
*
* If the label offset has not been set, returns the null value.
*/
public function get(int $offset) : ?string;
/**
* Returns the associated key for a specific label or all the keys.
*
* @return int[]
*/
public function keys(?string $label = null) : array;
/**
* Tells whether the domain is absolute.
*/
public function isAbsolute() : bool;
/**
* Prepends a label to the host.
*/
public function prepend(Stringable|string $label) : self;
/**
* Appends a label to the host.
*/
public function append(Stringable|string $label) : self;
/**
* Extracts a slice of $length elements starting at position $offset from the host.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the selected slice.
*
* If $length is null it returns all elements from $offset to the end of the Domain.
*/
public function slice(int $offset, int $length = null) : self;
/**
* Returns an instance with its Root label.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
public function withRootLabel() : self;
/**
* Returns an instance without its Root label.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
public function withoutRootLabel() : self;
/**
* Returns an instance with the modified label.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the new label
*
* If $key is non-negative, the added label will be the label at $key position from the start.
* If $key is negative, the added label will be the label at $key position from the end.
*
* @throws SyntaxError If the key is invalid
*/
public function withLabel(int $key, Stringable|string $label) : self;
/**
* Returns an instance without the specified label.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified component
*
* If $key is non-negative, the removed label will be the label at $key position from the start.
* If $key is negative, the removed label will be the label at $key position from the end.
*
* @throws SyntaxError If the key is invalid
*/
public function withoutLabel(int ...$keys) : self;
}

View File

@ -0,0 +1,20 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
interface FragmentInterface extends UriComponentInterface
{
/**
* Returns the decoded fragment.
*/
public function decoded() : ?string;
}

View File

@ -0,0 +1,48 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
interface HostInterface extends UriComponentInterface
{
/**
* Returns the ascii representation.
*/
public function toAscii() : ?string;
/**
* Returns the unicode representation.
*/
public function toUnicode() : ?string;
/**
* Returns the IP version.
*
* If the host is a not an IP this method will return null
*/
public function getIpVersion() : ?string;
/**
* Returns the IP component If the Host is an IP address.
*
* If the host is a not an IP this method will return null
*/
public function getIp() : ?string;
/**
* Tells whether the host is a domain name.
*/
public function isDomain() : bool;
/**
* Tells whether the host is an IP Address.
*/
public function isIp() : bool;
/**
* Tells whether the host is a registered name.
*/
public function isRegisteredName() : bool;
}

View File

@ -0,0 +1,43 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
interface IpHostInterface extends HostInterface
{
/**
* Tells whether the host is an IPv4 address.
*/
public function isIpv4() : bool;
/**
* Tells whether the host is an IPv6 address.
*/
public function isIpv6() : bool;
/**
* Tells whether the host is an IPv6 address.
*/
public function isIpFuture() : bool;
/**
* Tells whether the host has a ZoneIdentifier.
*
* @see http://tools.ietf.org/html/rfc6874#section-4
*/
public function hasZoneIdentifier() : bool;
/**
* Returns a host without its zone identifier according to RFC6874.
*
* This method MUST retain the state of the current instance, and return
* an instance without the host zone identifier according to RFC6874
*
* @see http://tools.ietf.org/html/rfc6874#section-4
*/
public function withoutZoneIdentifier() : self;
}

View File

@ -0,0 +1,80 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
interface PathInterface extends UriComponentInterface
{
/**
* Returns the decoded path.
*/
public function decoded() : string;
/**
* Tells whether the path is absolute or relative.
*/
public function isAbsolute() : bool;
/**
* Tells whether the path has a trailing slash.
*/
public function hasTrailingSlash() : bool;
/**
* Returns an instance without dot segments.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the path component normalized by removing
* the dot segment.
*
* @throws SyntaxError for invalid component or transformations
* that would result in a object in invalid state.
*/
public function withoutDotSegments() : self;
/**
* Returns an instance with a leading slash.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the path component with a leading slash
*
* @throws SyntaxError for invalid component or transformations
* that would result in a object in invalid state.
*/
public function withLeadingSlash() : self;
/**
* Returns an instance without a leading slash.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the path component without a leading slash
*
* @throws SyntaxError for invalid component or transformations
* that would result in a object in invalid state.
*/
public function withoutLeadingSlash() : self;
/**
* Returns an instance with a trailing slash.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the path component with a trailing slash
*
* @throws SyntaxError for invalid component or transformations
* that would result in a object in invalid state.
*/
public function withTrailingSlash() : self;
/**
* Returns an instance without a trailing slash.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the path component without a trailing slash
*
* @throws SyntaxError for invalid component or transformations
* that would result in a object in invalid state.
*/
public function withoutTrailingSlash() : self;
}

View File

@ -0,0 +1,20 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
interface PortInterface extends UriComponentInterface
{
/**
* Returns the integer representation of the Port.
*/
public function toInt() : ?int;
}

View File

@ -0,0 +1,227 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use Countable;
use Iterator;
use IteratorAggregate;
use Stringable;
/**
* @extends IteratorAggregate<array{0:string, 1:string|null}>
*
* @method self withoutPairByKey(string ...$keys) Returns an instance without pairs with the specified keys.
* @method self withoutPairByValue(Stringable|string|int|bool|null ...$values) Returns an instance without pairs with the specified values.
* @method self withoutPairByKeyValue(string $key, Stringable|string|int|bool|null $value) Returns an instance without pairs with the specified key/value pair
* @method bool hasPair(string $key, ?string $value) Tells whether the pair exists in the query.
* @method ?string toFormData() Returns the string representation using the applicat/www-form-urlencoded rules
* @method ?string toRFC3986() Returns the string representation using RFC3986 rules
*/
interface QueryInterface extends Countable, IteratorAggregate, UriComponentInterface
{
/**
* Returns the query separator.
*
* @return non-empty-string
*/
public function getSeparator() : string;
/**
* Returns the number of key/value pairs present in the object.
*/
public function count() : int;
/**
* Returns an iterator allowing to go through all key/value pairs contained in this object.
*
* The pair is represented as an array where the first value is the pair key
* and the second value the pair value.
*
* The key of each pair is a string
* The value of each pair is a scalar or the null value
*
* @return Iterator<int, array{0:string, 1:string|null}>
*/
public function getIterator() : Iterator;
/**
* Returns an iterator allowing to go through all key/value pairs contained in this object.
*
* The return type is as an Iterator where its offset is the pair key and its value the pair value.
*
* The key of each pair is a string
* The value of each pair is a scalar or the null value
*
* @return iterable<string, string|null>
*/
public function pairs() : iterable;
/**
* Tells whether a list of pair with a specific key exists.
*
* @see https://url.spec.whatwg.org/#dom-urlsearchparams-has
*/
public function has(string ...$keys) : bool;
/**
* Returns the first value associated to the given pair name.
*
* If no value is found null is returned
*
* @see https://url.spec.whatwg.org/#dom-urlsearchparams-get
*/
public function get(string $key) : ?string;
/**
* Returns all the values associated to the given pair name as an array or all
* the instance pairs.
*
* If no value is found an empty array is returned
*
* @see https://url.spec.whatwg.org/#dom-urlsearchparams-getall
*
* @return array<int, string|null>
*/
public function getAll(string $key) : array;
/**
* Returns the store PHP variables as elements of an array.
*
* The result is similar as PHP parse_str when used with its
* second argument with the difference that variable names are
* not mangled.
*
* @see http://php.net/parse_str
* @see https://wiki.php.net/rfc/on_demand_name_mangling
*
* @return array the collection of stored PHP variables or the empty array if no input is given,
*/
public function parameters() : array;
/**
* Returns the value attached to the specific key.
*
* The result is similar to PHP parse_str with the difference that variable
* names are not mangled.
*
* If a key is submitted it will return the value attached to it or null
*
* @see http://php.net/parse_str
* @see https://wiki.php.net/rfc/on_demand_name_mangling
*
* @return mixed the collection of stored PHP variables or the empty array if no input is given,
* the single value of a stored PHP variable or null if the variable is not present in the collection
*/
public function parameter(string $name) : mixed;
/**
* Tells whether a list of variable with specific names exists.
*
* @see https://url.spec.whatwg.org/#dom-urlsearchparams-has
*/
public function hasParameter(string ...$names) : bool;
/**
* Returns the RFC1738 encoded query.
*/
public function toRFC1738() : ?string;
/**
* Returns an instance with a different separator.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the query component with a different separator
*/
public function withSeparator(string $separator) : self;
/**
* Returns an instance with the new pairs set to it.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified query
*
* @see ::withPair
*/
public function merge(Stringable|string $query) : self;
/**
* Returns an instance with the new pairs appended to it.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified query
*
* If the pair already exists the value will be added to it.
*/
public function append(Stringable|string $query) : self;
/**
* Returns a new instance with a specified key/value pair appended as a new pair.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified query
*/
public function appendTo(string $key, Stringable|string|int|bool|null $value) : self;
/**
* Sorts the query string by offset, maintaining offset to data correlations.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified query
*
* @see https://url.spec.whatwg.org/#dom-urlsearchparams-sort
*/
public function sort() : self;
/**
* Returns an instance without duplicate key/value pair.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the query component normalized by removing
* duplicate pairs whose key/value are the same.
*/
public function withoutDuplicates() : self;
/**
* Returns an instance without empty key/value where the value is the null value.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the query component normalized by removing
* empty pairs.
*
* A pair is considered empty if its value is equal to the null value
*/
public function withoutEmptyPairs() : self;
/**
* Returns an instance where numeric indices associated to PHP's array like key are removed.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the query component normalized so that numeric indexes
* are removed from the pair key value.
*
* i.e.: toto[3]=bar[3]&foo=bar becomes toto[]=bar[3]&foo=bar
*/
public function withoutNumericIndices() : self;
/**
* Returns an instance with a new key/value pair added to it.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified query
*
* If the pair already exists the value will replace the existing value.
*
* @see https://url.spec.whatwg.org/#dom-urlsearchparams-set
*/
public function withPair(string $key, Stringable|string|int|float|bool|null $value) : self;
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.2.0
* @codeCoverageIgnore
* @see Modifier::removeQueryPairsByKey()
*
* Returns an instance without the specified keys.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified component
*/
public function withoutPair(string ...$keys) : self;
/**
* Returns an instance without the specified params.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified component without PHP's value.
* PHP's mangled is not taken into account.
*/
public function withoutParameters(string ...$names) : self;
}

View File

@ -0,0 +1,131 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use Countable;
use Iterator;
use IteratorAggregate;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use Stringable;
/**
* @extends IteratorAggregate<string>
*/
interface SegmentedPathInterface extends Countable, IteratorAggregate, PathInterface
{
/**
* Returns the total number of segments in the path.
*/
public function count() : int;
/**
* Iterate over the path segment.
*
* @return Iterator<string>
*/
public function getIterator() : Iterator;
/**
* Returns parent directory's path.
*/
public function getDirname() : string;
/**
* Returns the path basename.
*/
public function getBasename() : string;
/**
* Returns the basename extension.
*/
public function getExtension() : string;
/**
* Retrieves a single path segment.
*
* If the segment offset has not been set, returns null.
*/
public function get(int $offset) : ?string;
/**
* Returns the associated key for a specific segment.
*
* If a value is specified only the keys associated with
* the given value will be returned
*
* @return array<int>
*/
public function keys(Stringable|string|null $segment = null) : array;
/**
* Appends a segment to the path.
*/
public function append(Stringable|string $segment) : self;
/**
* Extracts a slice of $length elements starting at position $offset from the host.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the selected slice.
*
* If $length is null it returns all elements from $offset to the end of the Path.
*/
public function slice(int $offset, int $length = null) : self;
/**
* Prepends a segment to the path.
*/
public function prepend(Stringable|string $segment) : self;
/**
* Returns an instance with the modified segment.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the new segment
*
* If $key is non-negative, the added segment will be the segment at $key position from the start.
* If $key is negative, the added segment will be the segment at $key position from the end.
*
* @throws SyntaxError If the key is invalid
*/
public function withSegment(int $key, Stringable|string $segment) : self;
/**
* Returns an instance without the specified segment.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the modified component
*
* If $key is non-negative, the removed segment will be the segment at $key position from the start.
* If $key is negative, the removed segment will be the segment at $key position from the end.
*
* @throws SyntaxError If the key is invalid
*/
public function withoutSegment(int ...$keys) : self;
/**
* Returns an instance without duplicate delimiters.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the path component normalized by removing
* multiple consecutive empty segment
*/
public function withoutEmptySegments() : self;
/**
* Returns an instance with the specified parent directory's path.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the extension basename modified.
*/
public function withDirname(Stringable|string $path) : self;
/**
* Returns an instance with the specified basename.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the extension basename modified.
*/
public function withBasename(Stringable|string $basename) : self;
/**
* Returns an instance with the specified basename extension.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the extension basename modified.
*/
public function withExtension(Stringable|string $extension) : self;
}

View File

@ -0,0 +1,19 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use WP_Ultimo\Dependencies\Psr\Http\Message\UriInterface as Psr7UriInterface;
interface UriAccess
{
public function getUri() : UriInterface|Psr7UriInterface;
public function getUriString() : string;
}

View File

@ -0,0 +1,68 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use JsonSerializable;
use Stringable;
interface UriComponentInterface extends JsonSerializable, Stringable
{
/**
* Returns the instance string representation.
*
* If the instance is defined, the value returned MUST be percent-encoded,
* but MUST NOT double-encode any characters. To determine what characters
* to encode, please refer to RFC 3986, Sections 2 and 3.
*
* If the instance is not defined null is returned
*/
public function value() : ?string;
/**
* Returns the instance string representation.
*
* If the instance is defined, the value returned MUST be percent-encoded,
* but MUST NOT double-encode any characters. To determine what characters
* to encode, please refer to RFC 3986, Sections 2 and 3.
*
* If the instance is not defined an empty string is returned
*/
public function toString() : string;
/**
* Returns the instance string representation.
*
* If the instance is defined, the value returned MUST be percent-encoded,
* but MUST NOT double-encode any characters. To determine what characters
* to encode, please refer to RFC 3986, Sections 2 and 3.
*
* If the instance is not defined an empty string is returned
*/
public function __toString() : string;
/**
* Returns the instance json representation.
*
* If the instance is defined, the value returned MUST be percent-encoded,
* but MUST NOT double-encode any characters. To determine what characters
* to encode, please refer to RFC 3986 or RFC 1738.
*
* If the instance is not defined null is returned
*/
public function jsonSerialize() : ?string;
/**
* Returns the instance string representation with its optional URI delimiters.
*
* The value returned MUST be percent-encoded, but MUST NOT double-encode any
* characters. To determine what characters to encode, please refer to RFC 3986,
* Sections 2 and 3.
*
* If the instance is not defined an empty string is returned
*/
public function getUriComponent() : string;
}

View File

@ -0,0 +1,17 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use Throwable;
interface UriException extends Throwable
{
}

View File

@ -0,0 +1,289 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use JsonSerializable;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\MissingFeature;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use WP_Ultimo\Dependencies\League\Uri\UriString;
use Stringable;
/**
* @phpstan-import-type ComponentMap from UriString
*/
interface UriInterface extends JsonSerializable, Stringable
{
/**
* Returns the string representation as a URI reference.
*
* @see http://tools.ietf.org/html/rfc3986#section-4.1
*/
public function __toString() : string;
/**
* Returns the string representation as a URI reference.
*
* @see http://tools.ietf.org/html/rfc3986#section-4.1
*/
public function toString() : string;
/**
* Returns the string representation as a URI reference.
*
* @see http://tools.ietf.org/html/rfc3986#section-4.1
* @see ::__toString
*/
public function jsonSerialize() : string;
/**
* Retrieve the scheme component of the URI.
*
* If no scheme is present, this method MUST return a null value.
*
* The value returned MUST be normalized to lowercase, per RFC 3986
* Section 3.1.
*
* The trailing ":" character is not part of the scheme and MUST NOT be
* added.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.1
*/
public function getScheme() : ?string;
/**
* Retrieve the authority component of the URI.
*
* If no scheme is present, this method MUST return a null value.
*
* If the port component is not set or is the standard port for the current
* scheme, it SHOULD NOT be included.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2
*/
public function getAuthority() : ?string;
/**
* Retrieve the user information component of the URI.
*
* If no scheme is present, this method MUST return a null value.
*
* If a user is present in the URI, this will return that value;
* additionally, if the password is also present, it will be appended to the
* user value, with a colon (":") separating the values.
*
* The trailing "@" character is not part of the user information and MUST
* NOT be added.
*/
public function getUserInfo() : ?string;
/**
* Retrieve the host component of the URI.
*
* If no host is present this method MUST return a null value.
*
* The value returned MUST be normalized to lowercase, per RFC 3986
* Section 3.2.2.
*
* @see http://tools.ietf.org/html/rfc3986#section-3.2.2
*/
public function getHost() : ?string;
/**
* Retrieve the port component of the URI.
*
* If a port is present, and it is non-standard for the current scheme,
* this method MUST return it as an integer. If the port is the standard port
* used with the current scheme, this method SHOULD return null.
*
* If no port is present, and no scheme is present, this method MUST return
* a null value.
*
* If no port is present, but a scheme is present, this method MAY return
* the standard port for that scheme, but SHOULD return null.
*/
public function getPort() : ?int;
/**
* Retrieve the path component of the URI.
*
* The path can either be empty or absolute (starting with a slash) or
* rootless (not starting with a slash). Implementations MUST support all
* three syntaxes.
*
* Normally, the empty path "" and absolute path "/" are considered equal as
* defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
* do this normalization because in contexts with a trimmed base path, e.g.
* the front controller, this difference becomes significant. It's the task
* of the user to handle both "" and "/".
*
* The value returned MUST be percent-encoded, but MUST NOT double-encode
* any characters. To determine what characters to encode, please refer to
* RFC 3986, Sections 2 and 3.3.
*
* As an example, if the value should include a slash ("/") not intended as
* delimiter between path segments, that value MUST be passed in encoded
* form (e.g., "%2F") to the instance.
*
* @see https://tools.ietf.org/html/rfc3986#section-2
* @see https://tools.ietf.org/html/rfc3986#section-3.3
*/
public function getPath() : string;
/**
* Retrieve the query string of the URI.
*
* If no host is present this method MUST return a null value.
*
* The leading "?" character is not part of the query and MUST NOT be
* added.
*
* The value returned MUST be percent-encoded, but MUST NOT double-encode
* any characters. To determine what characters to encode, please refer to
* RFC 3986, Sections 2 and 3.4.
*
* As an example, if a value in a key/value pair of the query string should
* include an ampersand ("&") not intended as a delimiter between values,
* that value MUST be passed in encoded form (e.g., "%26") to the instance.
*
* @see https://tools.ietf.org/html/rfc3986#section-2
* @see https://tools.ietf.org/html/rfc3986#section-3.4
*/
public function getQuery() : ?string;
/**
* Retrieve the fragment component of the URI.
*
* If no host is present this method MUST return a null value.
*
* The leading "#" character is not part of the fragment and MUST NOT be
* added.
*
* The value returned MUST be percent-encoded, but MUST NOT double-encode
* any characters. To determine what characters to encode, please refer to
* RFC 3986, Sections 2 and 3.5.
*
* @see https://tools.ietf.org/html/rfc3986#section-2
* @see https://tools.ietf.org/html/rfc3986#section-3.5
*/
public function getFragment() : ?string;
/**
* Returns an associative array containing all the URI components.
*
* The returned array is similar to PHP's parse_url return value with the following
* differences:
*
* <ul>
* <li>All components are present in the returned array</li>
* <li>Empty and undefined component are treated differently. And empty component is
* set to the empty string while an undefined component is set to the `null` value.</li>
* </ul>
*
* @link https://tools.ietf.org/html/rfc3986
*
* @return ComponentMap
*/
public function getComponents() : array;
/**
* Return an instance with the specified scheme.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified scheme.
*
* A null value provided for the scheme is equivalent to removing the scheme
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withScheme(Stringable|string|null $scheme) : self;
/**
* Return an instance with the specified user information.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified user information.
*
* Password is optional, but the user information MUST include the
* user; a null value for the user is equivalent to removing user
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withUserInfo(Stringable|string|null $user, Stringable|string|null $password = null) : self;
/**
* Return an instance with the specified host.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified host.
*
* A null value provided for the host is equivalent to removing the host
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
* @throws MissingFeature for component or transformations
* requiring IDN support when IDN support is not present
* or misconfigured.
*/
public function withHost(Stringable|string|null $host) : self;
/**
* Return an instance with the specified port.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified port.
*
* A null value provided for the port is equivalent to removing the port
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withPort(?int $port) : self;
/**
* Return an instance with the specified path.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified path.
*
* The path can either be empty or absolute (starting with a slash) or
* rootless (not starting with a slash). Implementations MUST support all
* three syntaxes.
*
* Users can provide both encoded and decoded path characters.
* Implementations ensure the correct encoding as outlined in getPath().
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withPath(Stringable|string $path) : self;
/**
* Return an instance with the specified query string.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified query string.
*
* Users can provide both encoded and decoded query characters.
* Implementations ensure the correct encoding as outlined in getQuery().
*
* A null value provided for the query is equivalent to removing the query
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withQuery(Stringable|string|null $query) : self;
/**
* Return an instance with the specified URI fragment.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified URI fragment.
*
* Users can provide both encoded and decoded fragment characters.
* Implementations ensure the correct encoding as outlined in getFragment().
*
* A null value provided for the fragment is equivalent to removing the fragment
* information.
*
* @throws SyntaxError for invalid component or transformations
* that would result in an object in invalid state.
*/
public function withFragment(Stringable|string|null $fragment) : self;
}

View File

@ -0,0 +1,55 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Contracts;
use Stringable;
interface UserInfoInterface extends UriComponentInterface
{
/**
* Returns the user component part.
*/
public function getUser() : ?string;
/**
* Returns the pass component part.
*/
public function getPass() : ?string;
/**
* Returns an associative array containing all the User Info components.
*
* The returned a hashmap similar to PHP's parse_url return value
*
* @link https://tools.ietf.org/html/rfc3986
*
* @return array{user: ?string, pass : ?string}
*/
public function components() : array;
/**
* Returns an instance with the specified user and/or pass.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified new username
* otherwise it returns the same instance unchanged.
*
* A variable equal to null is equivalent to removing the complete user information.
*/
public function withUser(Stringable|string|null $username) : self;
/**
* Returns an instance with the specified user and/or pass.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the specified password if the user is specified
* otherwise it returns the same instance unchanged.
*
* An empty user is equivalent to removing the user information.
*/
public function withPass(Stringable|string|null $password) : self;
}

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 WP_Ultimo\Dependencies\League\Uri;
use Closure;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriComponentInterface;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use Stringable;
use function preg_match;
use function preg_replace_callback;
use function rawurldecode;
use function rawurlencode;
use function strtoupper;
final class Encoder
{
private const REGEXP_CHARS_INVALID = '/[\\x00-\\x1f\\x7f]/';
private const REGEXP_CHARS_ENCODED = ',%[A-Fa-f0-9]{2},';
private const REGEXP_CHARS_PREVENTS_DECODING = ',%
2[A-F|1-2|4-9]|
3[0-9|B|D]|
4[1-9|A-F]|
5[0-9|A|F]|
6[1-9|A-F]|
7[0-9|E]
,ix';
private const REGEXP_PART_SUBDELIM = "\\!\$&'\\(\\)\\*\\+,;\\=%";
private const REGEXP_PART_UNRESERVED = 'A-Za-z\\d_\\-.~';
private const REGEXP_PART_ENCODED = '%(?![A-Fa-f\\d]{2})';
/**
* Encode User.
*
* All generic delimiters MUST be encoded
*/
public static function encodeUser(Stringable|string|null $component) : ?string
{
static $pattern = '/[^' . self::REGEXP_PART_UNRESERVED . self::REGEXP_PART_SUBDELIM . ']+|' . self::REGEXP_PART_ENCODED . '/';
return self::encode($component, $pattern);
}
/**
* Encode Password.
*
* Generic delimiters ":" MUST NOT be encoded
*/
public static function encodePassword(Stringable|string|null $component) : ?string
{
static $pattern = '/[^' . self::REGEXP_PART_UNRESERVED . self::REGEXP_PART_SUBDELIM . ':]+|' . self::REGEXP_PART_ENCODED . '/';
return self::encode($component, $pattern);
}
/**
* Encode Path.
*
* Generic delimiters ":", "@", and "/" MUST NOT be encoded
*/
public static function encodePath(Stringable|string|null $component) : string
{
static $pattern = '/[^' . self::REGEXP_PART_UNRESERVED . self::REGEXP_PART_SUBDELIM . ':@\\/]+|' . self::REGEXP_PART_ENCODED . '/';
return (string) self::encode($component, $pattern);
}
/**
* Encode Query or Fragment.
*
* Generic delimiters ":", "@", "?", and "/" MUST NOT be encoded
*/
public static function encodeQueryOrFragment(Stringable|string|null $component) : ?string
{
static $pattern = '/[^' . self::REGEXP_PART_UNRESERVED . self::REGEXP_PART_SUBDELIM . ':@\\/?]+|' . self::REGEXP_PART_ENCODED . '/';
return self::encode($component, $pattern);
}
public static function encodeQueryKeyValue(mixed $component) : ?string
{
static $pattern = '/[^' . self::REGEXP_PART_UNRESERVED . ']+|' . self::REGEXP_PART_ENCODED . '/';
$encodeMatches = static fn(array $matches): string => match (1) {
preg_match('/[^' . self::REGEXP_PART_UNRESERVED . ']/', rawurldecode($matches[0])) => rawurlencode($matches[0]),
default => $matches[0],
};
$component = self::filterComponent($component);
return match (\true) {
!\is_scalar($component) => throw new SyntaxError(\sprintf('A pair key/value must be a scalar value `%s` given.', \gettype($component))),
1 === preg_match(self::REGEXP_CHARS_INVALID, $component) => rawurlencode($component),
1 === preg_match($pattern, $component) => (string) preg_replace_callback($pattern, $encodeMatches(...), $component),
default => $component,
};
}
/**
* Decodes the URI component without decoding the unreserved characters which are already encoded.
*/
public static function decodePartial(Stringable|string|int|null $component) : ?string
{
$decodeMatches = static fn(array $matches): string => match (1) {
preg_match(self::REGEXP_CHARS_PREVENTS_DECODING, $matches[0]) => strtoupper($matches[0]),
default => rawurldecode($matches[0]),
};
return self::decode($component, $decodeMatches);
}
/**
* Decodes all the URI component characters.
*/
public static function decodeAll(Stringable|string|int|null $component) : ?string
{
$decodeMatches = static fn(array $matches): string => rawurldecode($matches[0]);
return self::decode($component, $decodeMatches);
}
private static function filterComponent(mixed $component) : ?string
{
return match (\true) {
\true === $component => '1',
\false === $component => '0',
$component instanceof UriComponentInterface => $component->value(),
$component instanceof Stringable, \is_scalar($component) => (string) $component,
null === $component => null,
default => throw new SyntaxError(\sprintf('The component must be a scalar value `%s` given.', \gettype($component))),
};
}
private static function encode(Stringable|string|int|bool|null $component, string $pattern) : ?string
{
$component = self::filterComponent($component);
$encodeMatches = static fn(array $matches): string => match (1) {
preg_match('/[^' . self::REGEXP_PART_UNRESERVED . ']/', rawurldecode($matches[0])) => rawurlencode($matches[0]),
default => $matches[0],
};
return match (\true) {
null === $component, '' === $component => $component,
default => (string) preg_replace_callback($pattern, $encodeMatches(...), $component),
};
}
/**
* Decodes all the URI component characters.
*/
private static function decode(Stringable|string|int|null $component, Closure $decodeMatches) : ?string
{
$component = self::filterComponent($component);
return match (\true) {
null === $component => null,
1 === preg_match(self::REGEXP_CHARS_INVALID, $component) => throw new SyntaxError('Invalid component string: ' . $component . '.'),
1 === preg_match(self::REGEXP_CHARS_ENCODED, $component) => preg_replace_callback(self::REGEXP_CHARS_ENCODED, $decodeMatches(...), $component),
default => $component,
};
}
}

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 WP_Ultimo\Dependencies\League\Uri\Exceptions;
use WP_Ultimo\Dependencies\League\Uri\Idna\Error;
use WP_Ultimo\Dependencies\League\Uri\Idna\Result;
use Stringable;
final class ConversionFailed extends SyntaxError
{
private function __construct(string $message, private readonly string $host, private readonly Result $result)
{
parent::__construct($message);
}
public static function dueToIdnError(Stringable|string $host, Result $result) : self
{
$reasons = \array_map(fn(Error $error): string => $error->description(), $result->errors());
return new self('Host `' . $host . '` is invalid: ' . \implode('; ', $reasons) . '.', (string) $host, $result);
}
public function getHost() : string
{
return $this->host;
}
public function getResult() : Result
{
return $this->result;
}
}

View File

@ -0,0 +1,18 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Exceptions;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriException;
use RuntimeException;
class MissingFeature extends RuntimeException implements UriException
{
}

View File

@ -0,0 +1,16 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Exceptions;
class OffsetOutOfBounds extends SyntaxError
{
}

View File

@ -0,0 +1,18 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Exceptions;
use InvalidArgumentException;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriException;
class SyntaxError extends InvalidArgumentException implements UriException
{
}

View File

@ -0,0 +1,47 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use finfo;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\MissingFeature;
use WP_Ultimo\Dependencies\League\Uri\IPv4\Calculator;
use const PHP_INT_SIZE;
/**
* Allow detecting features needed to make the packages work.
*/
final class FeatureDetection
{
public static function supportsFileDetection() : void
{
static $isSupported = null;
$isSupported = $isSupported ?? \class_exists(finfo::class);
if (!$isSupported) {
throw new MissingFeature('Support for file type detection requires the `fileinfo` extension.');
}
}
public static function supportsIdn() : void
{
static $isSupported = null;
$isSupported = $isSupported ?? \function_exists('\\idn_to_ascii') && \defined('\\INTL_IDNA_VARIANT_UTS46');
if (!$isSupported) {
throw new MissingFeature('Support for IDN host requires the `intl` extension for best performance or run "composer require symfony/polyfill-intl-idn" to install a polyfill.');
}
}
public static function supportsIPv4Conversion() : void
{
static $isSupported = null;
$isSupported = $isSupported ?? \extension_loaded('gmp') || \function_exists('bcadd') || 4 < PHP_INT_SIZE;
if (!$isSupported) {
throw new MissingFeature('A ' . Calculator::class . ' implementation could not be automatically loaded. To perform IPv4 conversion use a x.64 PHP build or install one of the following extension GMP or BCMath. You can also ship your own implmentation.');
}
}
}

View File

@ -0,0 +1,67 @@
<?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 WP_Ultimo\Dependencies\League\Uri\IPv4;
use function bcadd;
use function bccomp;
use function bcdiv;
use function bcmod;
use function bcmul;
use function bcpow;
use function bcsub;
use function str_split;
final class BCMathCalculator implements Calculator
{
private const SCALE = 0;
private const CONVERSION_TABLE = ['0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', 'a' => '10', 'b' => '11', 'c' => '12', 'd' => '13', 'e' => '14', 'f' => '15'];
public function baseConvert(mixed $value, int $base) : string
{
$value = (string) $value;
if (10 === $base) {
return $value;
}
$base = (string) $base;
$decimal = '0';
foreach (str_split($value) as $char) {
$decimal = bcadd($this->multiply($decimal, $base), self::CONVERSION_TABLE[$char], self::SCALE);
}
return $decimal;
}
public function pow(mixed $value, int $exponent) : string
{
return bcpow((string) $value, (string) $exponent, self::SCALE);
}
public function compare(mixed $value1, $value2) : int
{
return bccomp((string) $value1, (string) $value2, self::SCALE);
}
public function multiply(mixed $value1, $value2) : string
{
return bcmul((string) $value1, (string) $value2, self::SCALE);
}
public function div(mixed $value, mixed $base) : string
{
return bcdiv((string) $value, (string) $base, self::SCALE);
}
public function mod(mixed $value, mixed $base) : string
{
return bcmod((string) $value, (string) $base, self::SCALE);
}
public function add(mixed $value1, mixed $value2) : string
{
return bcadd((string) $value1, (string) $value2, self::SCALE);
}
public function sub(mixed $value1, mixed $value2) : string
{
return bcsub((string) $value1, (string) $value2, self::SCALE);
}
}

View File

@ -0,0 +1,86 @@
<?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 WP_Ultimo\Dependencies\League\Uri\IPv4;
interface Calculator
{
/**
* Add numbers.
*
* @param mixed $value1 a number that will be added to $value2
* @param mixed $value2 a number that will be added to $value1
*
* @return mixed the addition result
*/
public function add(mixed $value1, mixed $value2);
/**
* Subtract one number from another.
*
* @param mixed $value1 a number that will be subtracted of $value2
* @param mixed $value2 a number that will be subtracted to $value1
*
* @return mixed the subtraction result
*/
public function sub(mixed $value1, mixed $value2);
/**
* Multiply numbers.
*
* @param mixed $value1 a number that will be multiplied by $value2
* @param mixed $value2 a number that will be multiplied by $value1
*
* @return mixed the multiplication result
*/
public function multiply(mixed $value1, mixed $value2);
/**
* Divide numbers.
*
* @param mixed $value The number being divided.
* @param mixed $base The number that $value is being divided by.
*
* @return mixed the result of the division
*/
public function div(mixed $value, mixed $base);
/**
* Raise an number to the power of exponent.
*
* @param mixed $value scalar, the base to use
*
* @return mixed the value raised to the power of exp.
*/
public function pow(mixed $value, int $exponent);
/**
* Returns the int point remainder (modulo) of the division of the arguments.
*
* @param mixed $value The dividend
* @param mixed $base The divisor
*
* @return mixed the remainder
*/
public function mod(mixed $value, mixed $base);
/**
* Number comparison.
*
* @param mixed $value1 the first value
* @param mixed $value2 the second value
*
* @return int Returns < 0 if value1 is less than value2; > 0 if value1 is greater than value2, and 0 if they are equal.
*/
public function compare(mixed $value1, mixed $value2) : int;
/**
* Get the decimal integer value of a variable.
*
* @param mixed $value The scalar value being converted to an integer
*
* @return mixed the integer value
*/
public function baseConvert(mixed $value, int $base);
}

View File

@ -0,0 +1,178 @@
<?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 WP_Ultimo\Dependencies\League\Uri\IPv4;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\MissingFeature;
use WP_Ultimo\Dependencies\League\Uri\FeatureDetection;
use Stringable;
use function array_pop;
use function count;
use function explode;
use function extension_loaded;
use function ltrim;
use function preg_match;
use function str_ends_with;
use function substr;
final class Converter
{
private const REGEXP_IPV4_HOST = '/
(?(DEFINE) # . is missing as it is used to separate labels
(?<hexadecimal>0x[[:xdigit:]]*)
(?<octal>0[0-7]*)
(?<decimal>\\d+)
(?<ipv4_part>(?:(?&hexadecimal)|(?&octal)|(?&decimal))*)
)
^(?:(?&ipv4_part)\\.){0,3}(?&ipv4_part)\\.?$
/x';
private const REGEXP_IPV4_NUMBER_PER_BASE = ['/^0x(?<number>[[:xdigit:]]*)$/' => 16, '/^0(?<number>[0-7]*)$/' => 8, '/^(?<number>\\d+)$/' => 10];
private readonly mixed $maxIPv4Number;
public function __construct(private readonly Calculator $calculator)
{
$this->maxIPv4Number = $calculator->sub($calculator->pow(2, 32), 1);
}
/**
* Returns an instance using a GMP calculator.
*/
public static function fromGMP() : self
{
return new self(new GMPCalculator());
}
/**
* Returns an instance using a Bcmath calculator.
*/
public static function fromBCMath() : self
{
return new self(new BCMathCalculator());
}
/**
* Returns an instance using a PHP native calculator (requires 64bits PHP).
*/
public static function fromNative() : self
{
return new self(new NativeCalculator());
}
/**
* Returns an instance using a detected calculator depending on the PHP environment.
*
* @throws MissingFeature If no Calculator implementing object can be used on the platform
*
* @codeCoverageIgnore
*/
public static function fromEnvironment() : self
{
FeatureDetection::supportsIPv4Conversion();
return match (\true) {
extension_loaded('gmp') => self::fromGMP(),
function_exists('bcadd') => self::fromBCMath(),
default => self::fromNative(),
};
}
public function isIpv4(Stringable|string|null $host) : bool
{
return null !== $this->toDecimal($host);
}
public function toOctal(Stringable|string|null $host) : ?string
{
$host = $this->toDecimal($host);
return match (null) {
$host => null,
default => \implode('.', \array_map(fn($value) => \str_pad(\decoct((int) $value), 4, '0', \STR_PAD_LEFT), explode('.', $host))),
};
}
public function toHexadecimal(Stringable|string|null $host) : ?string
{
$host = $this->toDecimal($host);
return match (null) {
$host => null,
default => '0x' . \implode('', \array_map(fn($value) => \dechex((int) $value), explode('.', $host))),
};
}
/**
* Tries to convert a IPv4 hexadecimal or a IPv4 octal notation into a IPv4 dot-decimal notation if possible
* otherwise returns null.
*
* @see https://url.spec.whatwg.org/#concept-ipv4-parser
*/
public function toDecimal(Stringable|string|null $host) : ?string
{
$host = (string) $host;
if (1 !== preg_match(self::REGEXP_IPV4_HOST, $host)) {
return null;
}
if (str_ends_with($host, '.')) {
$host = substr($host, 0, -1);
}
$numbers = [];
foreach (explode('.', $host) as $label) {
$number = $this->labelToNumber($label);
if (null === $number) {
return null;
}
$numbers[] = $number;
}
$ipv4 = array_pop($numbers);
$max = $this->calculator->pow(256, 6 - count($numbers));
if ($this->calculator->compare($ipv4, $max) > 0) {
return null;
}
foreach ($numbers as $offset => $number) {
if ($this->calculator->compare($number, 255) > 0) {
return null;
}
$ipv4 = $this->calculator->add($ipv4, $this->calculator->multiply($number, $this->calculator->pow(256, 3 - $offset)));
}
return $this->long2Ip($ipv4);
}
/**
* Converts a domain label into a IPv4 integer part.
*
* @see https://url.spec.whatwg.org/#ipv4-number-parser
*
* @return mixed returns null if it cannot correctly convert the label
*/
private function labelToNumber(string $label) : mixed
{
foreach (self::REGEXP_IPV4_NUMBER_PER_BASE as $regexp => $base) {
if (1 !== preg_match($regexp, $label, $matches)) {
continue;
}
$number = ltrim($matches['number'], '0');
if ('' === $number) {
return 0;
}
$number = $this->calculator->baseConvert($number, $base);
if (0 <= $this->calculator->compare($number, 0) && 0 >= $this->calculator->compare($number, $this->maxIPv4Number)) {
return $number;
}
}
return null;
}
/**
* Generates the dot-decimal notation for IPv4.
*
* @see https://url.spec.whatwg.org/#concept-ipv4-parser
*
* @param mixed $ipAddress the number representation of the IPV4address
*/
private function long2Ip(mixed $ipAddress) : string
{
$output = '';
for ($offset = 0; $offset < 4; $offset++) {
$output = $this->calculator->mod($ipAddress, 256) . $output;
if ($offset < 3) {
$output = '.' . $output;
}
$ipAddress = $this->calculator->div($ipAddress, 256);
}
return $output;
}
}

View File

@ -0,0 +1,58 @@
<?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 WP_Ultimo\Dependencies\League\Uri\IPv4;
use GMP;
use function gmp_add;
use function gmp_cmp;
use function gmp_div_q;
use function gmp_init;
use function gmp_mod;
use function gmp_mul;
use function gmp_pow;
use function gmp_sub;
use const GMP_ROUND_MINUSINF;
final class GMPCalculator implements Calculator
{
public function baseConvert(mixed $value, int $base) : GMP
{
return gmp_init($value, $base);
}
public function pow(mixed $value, int $exponent) : GMP
{
return gmp_pow($value, $exponent);
}
public function compare(mixed $value1, mixed $value2) : int
{
return gmp_cmp($value1, $value2);
}
public function multiply(mixed $value1, mixed $value2) : GMP
{
return gmp_mul($value1, $value2);
}
public function div(mixed $value, mixed $base) : GMP
{
return gmp_div_q($value, $base, GMP_ROUND_MINUSINF);
}
public function mod(mixed $value, mixed $base) : GMP
{
return gmp_mod($value, $base);
}
public function add(mixed $value1, mixed $value2) : GMP
{
return gmp_add($value1, $value2);
}
public function sub(mixed $value1, mixed $value2) : GMP
{
return gmp_sub($value1, $value2);
}
}

View File

@ -0,0 +1,50 @@
<?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 WP_Ultimo\Dependencies\League\Uri\IPv4;
use function floor;
use function intval;
final class NativeCalculator implements Calculator
{
public function baseConvert(mixed $value, int $base) : int
{
return intval((string) $value, $base);
}
public function pow(mixed $value, int $exponent)
{
return $value ** $exponent;
}
public function compare(mixed $value1, mixed $value2) : int
{
return $value1 <=> $value2;
}
public function multiply(mixed $value1, mixed $value2) : int
{
return $value1 * $value2;
}
public function div(mixed $value, mixed $base) : int
{
return (int) floor($value / $base);
}
public function mod(mixed $value, mixed $base) : int
{
return $value % $base;
}
public function add(mixed $value1, mixed $value2) : int
{
return $value1 + $value2;
}
public function sub(mixed $value1, mixed $value2) : int
{
return $value1 - $value2;
}
}

View File

@ -0,0 +1,177 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Idna;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\ConversionFailed;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use WP_Ultimo\Dependencies\League\Uri\FeatureDetection;
use Stringable;
use function idn_to_ascii;
use function idn_to_utf8;
use function rawurldecode;
use const INTL_IDNA_VARIANT_UTS46;
/**
* @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
*/
final class Converter
{
private const REGEXP_IDNA_PATTERN = '/[^\\x20-\\x7f]/';
private const MAX_DOMAIN_LENGTH = 253;
private const MAX_LABEL_LENGTH = 63;
/**
* General registered name regular expression.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
* @see https://regex101.com/r/fptU8V/1
*/
private const REGEXP_REGISTERED_NAME = '/
(?(DEFINE)
(?<unreserved>[a-z0-9_~\\-]) # . is missing as it is used to separate labels
(?<sub_delims>[!$&\'()*+,;=])
(?<encoded>%[A-F0-9]{2})
(?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
)
^(?:(?&reg_name)\\.)*(?&reg_name)\\.?$
/ix';
/**
* Converts the input to its IDNA ASCII form or throw on failure.
*
* @see Converter::toAscii()
*
* @throws SyntaxError if the string cannot be converted to UNICODE using IDN UTS46 algorithm
* @throws ConversionFailed if the conversion returns error
*/
public static function toAsciiOrFail(Stringable|string $domain, Option|int|null $options = null) : string
{
$result = self::toAscii($domain, $options);
return match (\true) {
$result->hasErrors() => throw ConversionFailed::dueToIdnError($domain, $result),
default => $result->domain(),
};
}
/**
* Converts the input to its IDNA ASCII form.
*
* This method returns the string converted to IDN ASCII form
*
* @throws SyntaxError if the string cannot be converted to ASCII using IDN UTS46 algorithm
*/
public static function toAscii(Stringable|string $domain, Option|int|null $options = null) : Result
{
$domain = rawurldecode((string) $domain);
if (1 === \preg_match(self::REGEXP_IDNA_PATTERN, $domain)) {
FeatureDetection::supportsIdn();
$flags = match (\true) {
null === $options => Option::forIDNA2008Ascii(),
$options instanceof Option => $options,
default => Option::new($options),
};
idn_to_ascii($domain, $flags->toBytes(), INTL_IDNA_VARIANT_UTS46, $idnaInfo);
if ([] === $idnaInfo) {
return Result::fromIntl(['result' => \strtolower($domain), 'isTransitionalDifferent' => \false, 'errors' => self::validateDomainAndLabelLength($domain)]);
}
return Result::fromIntl($idnaInfo);
}
$error = Error::NONE->value;
if (1 !== \preg_match(self::REGEXP_REGISTERED_NAME, $domain)) {
$error |= Error::DISALLOWED->value;
}
return Result::fromIntl(['result' => \strtolower($domain), 'isTransitionalDifferent' => \false, 'errors' => self::validateDomainAndLabelLength($domain) | $error]);
}
/**
* Converts the input to its IDNA UNICODE form or throw on failure.
*
* @see Converter::toUnicode()
*
* @throws ConversionFailed if the conversion returns error
*/
public static function toUnicodeOrFail(Stringable|string $domain, Option|int|null $options = null) : string
{
$result = self::toUnicode($domain, $options);
return match (\true) {
$result->hasErrors() => throw ConversionFailed::dueToIdnError($domain, $result),
default => $result->domain(),
};
}
/**
* Converts the input to its IDNA UNICODE form.
*
* This method returns the string converted to IDN UNICODE form
*
* @throws SyntaxError if the string cannot be converted to UNICODE using IDN UTS46 algorithm
*/
public static function toUnicode(Stringable|string $domain, Option|int|null $options = null) : Result
{
$domain = rawurldecode((string) $domain);
if (\false === \stripos($domain, 'xn--')) {
return Result::fromIntl(['result' => $domain, 'isTransitionalDifferent' => \false, 'errors' => Error::NONE->value]);
}
FeatureDetection::supportsIdn();
$flags = match (\true) {
null === $options => Option::forIDNA2008Unicode(),
$options instanceof Option => $options,
default => Option::new($options),
};
idn_to_utf8($domain, $flags->toBytes(), INTL_IDNA_VARIANT_UTS46, $idnaInfo);
if ([] === $idnaInfo) {
return Result::fromIntl(['result' => $domain, 'isTransitionalDifferent' => \false, 'errors' => Error::NONE->value]);
}
return Result::fromIntl($idnaInfo);
}
/**
* Tells whether the submitted host is a valid IDN regardless of its format.
*
* Returns false if the host is invalid or if its conversion yield the same result
*/
public static function isIdn(Stringable|string|null $domain) : bool
{
$domain = \strtolower(rawurldecode((string) $domain));
$result = match (1) {
\preg_match(self::REGEXP_IDNA_PATTERN, $domain) => self::toAscii($domain),
default => self::toUnicode($domain),
};
return match (\true) {
$result->hasErrors() => \false,
default => $result->domain() !== $domain,
};
}
/**
* Adapted from https://github.com/TRowbotham/idna.
*
* @see https://github.com/TRowbotham/idna/blob/master/src/Idna.php#L236
*/
private static function validateDomainAndLabelLength(string $domain) : int
{
$error = Error::NONE->value;
$labels = \explode('.', $domain);
$maxDomainSize = self::MAX_DOMAIN_LENGTH;
$length = \count($labels);
// If the last label is empty, and it is not the first label, then it is the root label.
// Increase the max size by 1, making it 254, to account for the root label's "."
// delimiter. This also means we don't need to check the last label's length for being too
// long.
if ($length > 1 && '' === $labels[$length - 1]) {
++$maxDomainSize;
\array_pop($labels);
}
if (\strlen($domain) > $maxDomainSize) {
$error |= Error::DOMAIN_NAME_TOO_LONG->value;
}
foreach ($labels as $label) {
if (\strlen($label) > self::MAX_LABEL_LENGTH) {
$error |= Error::LABEL_TOO_LONG->value;
break;
}
}
return $error;
}
}

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.
*/
namespace WP_Ultimo\Dependencies\League\Uri\Idna;
enum Error : int
{
case NONE = 0;
case EMPTY_LABEL = 1;
case LABEL_TOO_LONG = 2;
case DOMAIN_NAME_TOO_LONG = 4;
case LEADING_HYPHEN = 8;
case TRAILING_HYPHEN = 0x10;
case HYPHEN_3_4 = 0x20;
case LEADING_COMBINING_MARK = 0x40;
case DISALLOWED = 0x80;
case PUNYCODE = 0x100;
case LABEL_HAS_DOT = 0x200;
case INVALID_ACE_LABEL = 0x400;
case BIDI = 0x800;
case CONTEXTJ = 0x1000;
case CONTEXTO_PUNCTUATION = 0x2000;
case CONTEXTO_DIGITS = 0x4000;
public function description() : string
{
return match ($this) {
self::NONE => 'No error has occurred',
self::EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty',
self::LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes',
self::DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form',
self::LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")',
self::TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")',
self::HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions',
self::LEADING_COMBINING_MARK => 'a label starts with a combining mark',
self::DISALLOWED => 'a label or domain name contains disallowed characters',
self::PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode',
self::LABEL_HAS_DOT => 'a label contains a dot=full stop',
self::INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string',
self::BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)',
self::CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements',
self::CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits',
self::CONTEXTO_PUNCTUATION => 'a label does not meet the IDNA CONTEXTO requirements for punctuation characters. Some punctuation characters "Would otherwise have been DISALLOWED" but are allowed in certain contexts',
};
}
public static function filterByErrorBytes(int $errors) : array
{
return \array_values(\array_filter(self::cases(), fn(self $error): bool => 0 !== ($error->value & $errors)));
}
}

View File

@ -0,0 +1,137 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Idna;
use ReflectionClass;
use ReflectionClassConstant;
/**
* @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
*/
final class Option
{
private const DEFAULT = 0;
private const ALLOW_UNASSIGNED = 1;
private const USE_STD3_RULES = 2;
private const CHECK_BIDI = 4;
private const CHECK_CONTEXTJ = 8;
private const NONTRANSITIONAL_TO_ASCII = 0x10;
private const NONTRANSITIONAL_TO_UNICODE = 0x20;
private const CHECK_CONTEXTO = 0x40;
private function __construct(private readonly int $value)
{
}
private static function cases() : array
{
static $assoc;
if (null === $assoc) {
$assoc = [];
$fooClass = new ReflectionClass(self::class);
foreach ($fooClass->getConstants(ReflectionClassConstant::IS_PRIVATE) as $name => $value) {
$assoc[$name] = $value;
}
}
return $assoc;
}
public static function new(int $bytes = self::DEFAULT) : self
{
return new self(\array_reduce(self::cases(), fn(int $value, int $option) => 0 !== ($option & $bytes) ? $value | $option : $value, self::DEFAULT));
}
public static function forIDNA2008Ascii() : self
{
return self::new()->nonTransitionalToAscii()->checkBidi()->useSTD3Rules()->checkContextJ();
}
public static function forIDNA2008Unicode() : self
{
return self::new()->nonTransitionalToUnicode()->checkBidi()->useSTD3Rules()->checkContextJ();
}
public function toBytes() : int
{
return $this->value;
}
/** array<string, int> */
public function list() : array
{
return \array_keys(\array_filter(self::cases(), fn(int $value) => 0 !== ($value & $this->value)));
}
public function allowUnassigned() : self
{
return $this->add(self::ALLOW_UNASSIGNED);
}
public function disallowUnassigned() : self
{
return $this->remove(self::ALLOW_UNASSIGNED);
}
public function useSTD3Rules() : self
{
return $this->add(self::USE_STD3_RULES);
}
public function prohibitSTD3Rules() : self
{
return $this->remove(self::USE_STD3_RULES);
}
public function checkBidi() : self
{
return $this->add(self::CHECK_BIDI);
}
public function ignoreBidi() : self
{
return $this->remove(self::CHECK_BIDI);
}
public function checkContextJ() : self
{
return $this->add(self::CHECK_CONTEXTJ);
}
public function ignoreContextJ() : self
{
return $this->remove(self::CHECK_CONTEXTJ);
}
public function checkContextO() : self
{
return $this->add(self::CHECK_CONTEXTO);
}
public function ignoreContextO() : self
{
return $this->remove(self::CHECK_CONTEXTO);
}
public function nonTransitionalToAscii() : self
{
return $this->add(self::NONTRANSITIONAL_TO_ASCII);
}
public function transitionalToAscii() : self
{
return $this->remove(self::NONTRANSITIONAL_TO_ASCII);
}
public function nonTransitionalToUnicode() : self
{
return $this->add(self::NONTRANSITIONAL_TO_UNICODE);
}
public function transitionalToUnicode() : self
{
return $this->remove(self::NONTRANSITIONAL_TO_UNICODE);
}
public function add(Option|int|null $option = null) : self
{
return match (\true) {
null === $option => $this,
$option instanceof self => self::new($this->value | $option->value),
default => self::new($this->value | $option),
};
}
public function remove(Option|int|null $option = null) : self
{
return match (\true) {
null === $option => $this,
$option instanceof self => self::new($this->value & ~$option->value),
default => self::new($this->value & ~$option),
};
}
}

View File

@ -0,0 +1,57 @@
<?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 WP_Ultimo\Dependencies\League\Uri\Idna;
/**
* @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
*/
final class Result
{
private function __construct(
private readonly string $domain,
private readonly bool $isTransitionalDifferent,
/** @var array<Error> */
private readonly array $errors
)
{
}
/**
* @param array{result:string, isTransitionalDifferent:bool, errors:int} $infos
*/
public static function fromIntl(array $infos) : self
{
return new self($infos['result'], $infos['isTransitionalDifferent'], Error::filterByErrorBytes($infos['errors']));
}
public function domain() : string
{
return $this->domain;
}
public function isTransitionalDifferent() : bool
{
return $this->isTransitionalDifferent;
}
/**
* @return array<Error>
*/
public function errors() : array
{
return $this->errors;
}
public function hasErrors() : bool
{
return [] !== $this->errors;
}
public function hasError(Error $error) : bool
{
return \in_array($error, $this->errors, \true);
}
}

View File

@ -0,0 +1,178 @@
<?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 WP_Ultimo\Dependencies\League\Uri\KeyValuePair;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriComponentInterface;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use Stringable;
use function array_combine;
use function explode;
use function implode;
use function is_float;
use function is_int;
use function is_string;
use function json_encode;
use function preg_match;
use function str_replace;
use const JSON_PRESERVE_ZERO_FRACTION;
use const PHP_QUERY_RFC1738;
use const PHP_QUERY_RFC3986;
final class Converter
{
private const REGEXP_INVALID_CHARS = '/[\\x00-\\x1f\\x7f]/';
/** @var non-empty-string */
private readonly string $separator;
/**
* @param array<string> $fromRfc3986 contains all the RFC3986 encoded characters to be converted
* @param array<string> $toEncoding contains all the expected encoded characters
*/
private function __construct(string $separator, private readonly array $fromRfc3986 = [], private readonly array $toEncoding = [])
{
if ('' === $separator) {
throw new SyntaxError('The separator character must be a non empty string.');
}
$this->separator = $separator;
}
/**
* @param non-empty-string $separator
*/
public static function new(string $separator) : self
{
return new self($separator);
}
/**
* @param non-empty-string $separator
*/
public static function fromRFC3986(string $separator = '&') : self
{
return self::new($separator);
}
/**
* @param non-empty-string $separator
*/
public static function fromRFC1738(string $separator = '&') : self
{
return self::new($separator)->withEncodingMap(['%20' => '+']);
}
/**
* @param non-empty-string $separator
*
* @see https://url.spec.whatwg.org/#application/x-www-form-urlencoded
*/
public static function fromFormData(string $separator = '&') : self
{
return self::new($separator)->withEncodingMap(['%20' => '+', '%2A' => '*']);
}
public static function fromEncodingType(int $encType) : self
{
return match ($encType) {
PHP_QUERY_RFC3986 => self::fromRFC3986(),
PHP_QUERY_RFC1738 => self::fromRFC1738(),
default => throw new SyntaxError('Unknown or Unsupported encoding.'),
};
}
/**
* @return non-empty-string
*/
public function separator() : string
{
return $this->separator;
}
/**
* @return array<string, string>
*/
public function encodingMap() : array
{
return array_combine($this->fromRfc3986, $this->toEncoding);
}
/**
* @return array<non-empty-list<string|null>>
*/
public function toPairs(Stringable|string|int|float|bool|null $value) : array
{
$value = match (\true) {
$value instanceof UriComponentInterface => $value->value(),
$value instanceof Stringable, is_int($value) => (string) $value,
\false === $value => '0',
\true === $value => '1',
default => $value,
};
if (null === $value) {
return [];
}
$value = match (1) {
preg_match(self::REGEXP_INVALID_CHARS, (string) $value) => throw new SyntaxError('Invalid query string: `' . $value . '`.'),
default => str_replace($this->toEncoding, $this->fromRfc3986, (string) $value),
};
return \array_map(fn(string $pair): array => explode('=', $pair, 2) + [1 => null], explode($this->separator, $value));
}
private static function vString(Stringable|string|bool|int|float|null $value) : ?string
{
return match (\true) {
$value => '1',
\false === $value => '0',
null === $value => null,
is_float($value) => (string) json_encode($value, JSON_PRESERVE_ZERO_FRACTION),
default => (string) $value,
};
}
/**
* @param iterable<array{0:string|null, 1:Stringable|string|bool|int|float|null}> $pairs
*/
public function toValue(iterable $pairs) : ?string
{
$filteredPairs = [];
foreach ($pairs as $pair) {
$filteredPairs[] = match (\true) {
!is_string($pair[0]) => throw new SyntaxError('the pair key MUST be a string;, `' . \gettype($pair[0]) . '` given.'),
null === $pair[1] => self::vString($pair[0]),
default => self::vString($pair[0]) . '=' . self::vString($pair[1]),
};
}
return match ([]) {
$filteredPairs => null,
default => str_replace($this->fromRfc3986, $this->toEncoding, implode($this->separator, $filteredPairs)),
};
}
/**
* @param non-empty-string $separator
*/
public function withSeparator(string $separator) : self
{
return match ($this->separator) {
$separator => $this,
default => new self($separator, $this->fromRfc3986, $this->toEncoding),
};
}
/**
* Sets the conversion map.
*
* Each key from the iterable structure represents the RFC3986 encoded characters as string,
* while each value represents the expected output encoded characters
*/
public function withEncodingMap(iterable $encodingMap) : self
{
$fromRfc3986 = [];
$toEncoding = [];
foreach ($encodingMap as $from => $to) {
[$fromRfc3986[], $toEncoding[]] = match (\true) {
!is_string($from) => throw new SyntaxError('The encoding output must be a string; `' . \gettype($from) . '` given.'),
$to instanceof Stringable, is_string($to) => [$from, (string) $to],
default => throw new SyntaxError('The encoding output must be a string; `' . \gettype($to) . '` given.'),
};
}
return match (\true) {
$fromRfc3986 !== $this->fromRfc3986, $toEncoding !== $this->toEncoding => new self($this->separator, $fromRfc3986, $toEncoding),
default => $this,
};
}
}

View File

@ -0,0 +1,231 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use WP_Ultimo\Dependencies\League\Uri\KeyValuePair\Converter;
use Stringable;
use function array_key_exists;
use function array_keys;
use function is_array;
use function rawurldecode;
use function strpos;
use function substr;
use const PHP_QUERY_RFC3986;
/**
* A class to parse the URI query string.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.4
*/
final class QueryString
{
private const PAIR_VALUE_DECODED = 1;
private const PAIR_VALUE_PRESERVED = 2;
/**
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* Build a query string from a list of pairs.
*
* @see QueryString::buildFromPairs()
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
*
* @param iterable<array{0:string, 1:string|float|int|bool|null}> $pairs
* @param non-empty-string $separator
*
* @throws SyntaxError If the encoding type is invalid
* @throws SyntaxError If a pair is invalid
*/
public static function build(iterable $pairs, string $separator = '&', int $encType = PHP_QUERY_RFC3986) : ?string
{
return self::buildFromPairs($pairs, Converter::fromEncodingType($encType)->withSeparator($separator));
}
/**
* Build a query string from a list of pairs.
*
* The method expects the return value from Query::parse to build
* a valid query string. This method differs from PHP http_build_query as
* it does not modify parameters keys.
*
* If a reserved character is found in a URI component and
* no delimiting role is known for that character, then it must be
* interpreted as representing the data octet corresponding to that
* character's encoding in US-ASCII.
*
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
*
* @param iterable<array{0:string, 1:string|float|int|bool|null}> $pairs
*
* @throws SyntaxError If the encoding type is invalid
* @throws SyntaxError If a pair is invalid
*/
public static function buildFromPairs(iterable $pairs, Converter $converter = null) : ?string
{
$keyValuePairs = [];
foreach ($pairs as $pair) {
if (!is_array($pair) || [0, 1] !== array_keys($pair)) {
throw new SyntaxError('A pair must be a sequential array starting at `0` and containing two elements.');
}
$keyValuePairs[] = [(string) Encoder::encodeQueryKeyValue($pair[0]), match (null) {
$pair[1] => null,
default => Encoder::encodeQueryKeyValue($pair[1]),
}];
}
return ($converter ?? Converter::fromRFC3986())->toValue($keyValuePairs);
}
/**
* Parses the query string like parse_str without mangling the results.
*
* @see QueryString::extractFromValue()
* @see http://php.net/parse_str
* @see https://wiki.php.net/rfc/on_demand_name_mangling
*
* @param non-empty-string $separator
*
* @throws SyntaxError
*/
public static function extract(Stringable|string|bool|null $query, string $separator = '&', int $encType = PHP_QUERY_RFC3986) : array
{
return self::extractFromValue($query, Converter::fromEncodingType($encType)->withSeparator($separator));
}
/**
* Parses the query string like parse_str without mangling the results.
*
* The result is similar as PHP parse_str when used with its
* second argument with the difference that variable names are
* not mangled.
*
* @see http://php.net/parse_str
* @see https://wiki.php.net/rfc/on_demand_name_mangling
*
* @throws SyntaxError
*/
public static function extractFromValue(Stringable|string|bool|null $query, Converter $converter = null) : array
{
return self::convert(self::decodePairs(($converter ?? Converter::fromRFC3986())->toPairs($query), self::PAIR_VALUE_PRESERVED));
}
/**
* Parses a query string into a collection of key/value pairs.
*
* @param non-empty-string $separator
*
* @throws SyntaxError
*
* @return array<int, array{0:string, 1:string|null}>
*/
public static function parse(Stringable|string|bool|null $query, string $separator = '&', int $encType = PHP_QUERY_RFC3986) : array
{
return self::parseFromValue($query, Converter::fromEncodingType($encType)->withSeparator($separator));
}
/**
* Parses a query string into a collection of key/value pairs.
*
* @throws SyntaxError
*
* @return array<int, array{0:string, 1:string|null}>
*/
public static function parseFromValue(Stringable|string|bool|null $query, Converter $converter = null) : array
{
return self::decodePairs(($converter ?? Converter::fromRFC3986())->toPairs($query), self::PAIR_VALUE_DECODED);
}
/**
* @param array<non-empty-list<string|null>> $pairs
*
* @return array<int, array{0:string, 1:string|null}>
*/
private static function decodePairs(array $pairs, int $pairValueState) : array
{
$decodePair = static function (array $pair, int $pairValueState) : array {
[$key, $value] = $pair;
return match ($pairValueState) {
self::PAIR_VALUE_PRESERVED => [(string) Encoder::decodeAll($key), $value],
default => [(string) Encoder::decodeAll($key), Encoder::decodeAll($value)],
};
};
return \array_reduce($pairs, fn(array $carry, array $pair) => [...$carry, $decodePair($pair, $pairValueState)], []);
}
/**
* Converts a collection of key/value pairs and returns
* the store PHP variables as elements of an array.
*/
public static function convert(iterable $pairs) : array
{
$returnedValue = [];
foreach ($pairs as $pair) {
$returnedValue = self::extractPhpVariable($returnedValue, $pair);
}
return $returnedValue;
}
/**
* Parses a query pair like parse_str without mangling the results array keys.
*
* <ul>
* <li>empty name are not saved</li>
* <li>If the value from name is duplicated its corresponding value will be overwritten</li>
* <li>if no "[" is detected the value is added to the return array with the name as index</li>
* <li>if no "]" is detected after detecting a "[" the value is added to the return array with the name as index</li>
* <li>if there's a mismatch in bracket usage the remaining part is dropped</li>
* <li>“.” and “ ” are not converted to “_”</li>
* <li>If there is no “]”, then the first “[” is not converted to becomes an “_”</li>
* <li>no whitespace trimming is done on the key value</li>
* </ul>
*
* @see https://php.net/parse_str
* @see https://wiki.php.net/rfc/on_demand_name_mangling
* @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic1.phpt
* @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic2.phpt
* @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic3.phpt
* @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic4.phpt
*
* @param array $data the submitted array
* @param array|string $name the pair key
* @param string $value the pair value
*/
private static function extractPhpVariable(array $data, array|string $name, string $value = '') : array
{
if (is_array($name)) {
[$name, $value] = $name;
$value = rawurldecode((string) $value);
}
if ('' === $name) {
return $data;
}
$leftBracketPosition = strpos($name, '[');
if (\false === $leftBracketPosition) {
$data[$name] = $value;
return $data;
}
$rightBracketPosition = strpos($name, ']', $leftBracketPosition);
if (\false === $rightBracketPosition) {
$data[$name] = $value;
return $data;
}
$key = substr($name, 0, $leftBracketPosition);
if (!array_key_exists($key, $data) || !is_array($data[$key])) {
$data[$key] = [];
}
$index = substr($name, $leftBracketPosition + 1, $rightBracketPosition - $leftBracketPosition - 1);
if ('' === $index) {
$data[$key][] = $value;
return $data;
}
$remaining = substr($name, $rightBracketPosition + 1);
if (!\str_starts_with($remaining, '[') || \false === strpos($remaining, ']', 1)) {
$remaining = '';
}
$data[$key] = self::extractPhpVariable($data[$key], $index . $remaining, $value);
return $data;
}
}

View File

@ -0,0 +1,367 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\ConversionFailed;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\MissingFeature;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use WP_Ultimo\Dependencies\League\Uri\Idna\Converter;
use Stringable;
use function array_merge;
use function explode;
use function filter_var;
use function inet_pton;
use function preg_match;
use function rawurldecode;
use function sprintf;
use function strpos;
use function substr;
use const FILTER_FLAG_IPV6;
use const FILTER_VALIDATE_IP;
/**
* A class to parse a URI string according to RFC3986.
*
* @link https://tools.ietf.org/html/rfc3986
* @package League\Uri
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @since 6.0.0
*
* @phpstan-type AuthorityMap array{user:?string, pass:?string, host:?string, port:?int}
* @phpstan-type ComponentMap array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string}
* @phpstan-type InputComponentMap array{scheme? : ?string, user? : ?string, pass? : ?string, host? : ?string, port? : ?int, path? : ?string, query? : ?string, fragment? : ?string}
*/
final class UriString
{
/**
* Default URI component values.
*/
private const URI_COMPONENTS = ['scheme' => null, 'user' => null, 'pass' => null, 'host' => null, 'port' => null, 'path' => '', 'query' => null, 'fragment' => null];
/**
* Simple URI which do not need any parsing.
*/
private const URI_SCHORTCUTS = ['' => [], '#' => ['fragment' => ''], '?' => ['query' => ''], '?#' => ['query' => '', 'fragment' => ''], '/' => ['path' => '/'], '//' => ['host' => '']];
/**
* Range of invalid characters in URI string.
*/
private const REGEXP_INVALID_URI_CHARS = '/[\\x00-\\x1f\\x7f]/';
/**
* RFC3986 regular expression URI splitter.
*
* @link https://tools.ietf.org/html/rfc3986#appendix-B
*/
private const REGEXP_URI_PARTS = ',^
(?<scheme>(?<scontent>[^:/?\\#]+):)? # URI scheme component
(?<authority>//(?<acontent>[^/?\\#]*))? # URI authority part
(?<path>[^?\\#]*) # URI path component
(?<query>\\?(?<qcontent>[^\\#]*))? # URI query component
(?<fragment>\\#(?<fcontent>.*))? # URI fragment component
,x';
/**
* URI scheme regular expresssion.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.1
*/
private const REGEXP_URI_SCHEME = '/^([a-z][a-z\\d+.-]*)?$/i';
/**
* IPvFuture regular expression.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
private const REGEXP_IP_FUTURE = '/^
v(?<version>[A-F0-9])+\\.
(?:
(?<unreserved>[a-z0-9_~\\-\\.])|
(?<sub_delims>[!$&\'()*+,;=:]) # also include the : character
)+
$/ix';
/**
* General registered name regular expression.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
private const REGEXP_REGISTERED_NAME = '/(?(DEFINE)
(?<unreserved>[a-z0-9_~\\-]) # . is missing as it is used to separate labels
(?<sub_delims>[!$&\'()*+,;=])
(?<encoded>%[A-F0-9]{2})
(?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
)
^(?:(?&reg_name)\\.)*(?&reg_name)\\.?$/ix';
/**
* Invalid characters in host regular expression.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
private const REGEXP_INVALID_HOST_CHARS = '/
[:\\/?#\\[\\]@ ] # gen-delims characters as well as the space character
/ix';
/**
* Invalid path for URI without scheme and authority regular expression.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.3
*/
private const REGEXP_INVALID_PATH = ',^(([^/]*):)(.*)?/,';
/**
* Host and Port splitter regular expression.
*/
private const REGEXP_HOST_PORT = ',^(?<host>\\[.*\\]|[^:]*)(:(?<port>.*))?$,';
/**
* IDN Host detector regular expression.
*/
private const REGEXP_IDN_PATTERN = '/[^\\x20-\\x7f]/';
/**
* Only the address block fe80::/10 can have a Zone ID attach to
* let's detect the link local significant 10 bits.
*/
private const ZONE_ID_ADDRESS_BLOCK = "\xfe\x80";
/**
* Generate a URI string representation from its parsed representation
* returned by League\UriString::parse() or PHP's parse_url.
*
* If you supply your own array, you are responsible for providing
* valid components without their URI delimiters.
*
* @link https://tools.ietf.org/html/rfc3986#section-5.3
* @link https://tools.ietf.org/html/rfc3986#section-7.5
*
* @param InputComponentMap $components
*/
public static function build(array $components) : string
{
$uri = $components['path'] ?? '';
if (isset($components['query'])) {
$uri .= '?' . $components['query'];
}
if (isset($components['fragment'])) {
$uri .= '#' . $components['fragment'];
}
$scheme = null;
if (isset($components['scheme'])) {
$scheme = $components['scheme'] . ':';
}
$authority = self::buildAuthority($components);
if (null !== $authority) {
$authority = '//' . $authority;
}
return $scheme . $authority . $uri;
}
/**
* Generate a URI authority representation from its parsed representation.
*
* @param InputComponentMap $components
*/
public static function buildAuthority(array $components) : ?string
{
if (!isset($components['host'])) {
return null;
}
$authority = $components['host'];
if (isset($components['port'])) {
$authority .= ':' . $components['port'];
}
if (!isset($components['user'])) {
return $authority;
}
$authority = '@' . $authority;
if (!isset($components['pass'])) {
return $components['user'] . $authority;
}
return $components['user'] . ':' . $components['pass'] . $authority;
}
/**
* Parse a URI string into its components.
*
* This method parses a URI and returns an associative array containing any
* of the various components of the URI that are present.
*
* <code>
* $components = UriString::parse('http://foo@test.example.com:42?query#');
* var_export($components);
* //will display
* array(
* 'scheme' => 'http', // the URI scheme component
* 'user' => 'foo', // the URI user component
* 'pass' => null, // the URI pass component
* 'host' => 'test.example.com', // the URI host component
* 'port' => 42, // the URI port component
* 'path' => '', // the URI path component
* 'query' => 'query', // the URI query component
* 'fragment' => '', // the URI fragment component
* );
* </code>
*
* The returned array is similar to PHP's parse_url return value with the following
* differences:
*
* <ul>
* <li>All components are always present in the returned array</li>
* <li>Empty and undefined component are treated differently. And empty component is
* set to the empty string while an undefined component is set to the `null` value.</li>
* <li>The path component is never undefined</li>
* <li>The method parses the URI following the RFC3986 rules, but you are still
* required to validate the returned components against its related scheme specific rules.</li>
* </ul>
*
* @link https://tools.ietf.org/html/rfc3986
*
* @throws SyntaxError if the URI contains invalid characters
* @throws SyntaxError if the URI contains an invalid scheme
* @throws SyntaxError if the URI contains an invalid path
*
* @return ComponentMap
*/
public static function parse(Stringable|string|int $uri) : array
{
$uri = (string) $uri;
if (isset(self::URI_SCHORTCUTS[$uri])) {
/** @var ComponentMap $components */
$components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]);
return $components;
}
if (1 === preg_match(self::REGEXP_INVALID_URI_CHARS, $uri)) {
throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri));
}
//if the first character is a known URI delimiter parsing can be simplified
$first_char = $uri[0];
//The URI is made of the fragment only
if ('#' === $first_char) {
[, $fragment] = explode('#', $uri, 2);
$components = self::URI_COMPONENTS;
$components['fragment'] = $fragment;
return $components;
}
//The URI is made of the query and fragment
if ('?' === $first_char) {
[, $partial] = explode('?', $uri, 2);
[$query, $fragment] = explode('#', $partial, 2) + [1 => null];
$components = self::URI_COMPONENTS;
$components['query'] = $query;
$components['fragment'] = $fragment;
return $components;
}
//use RFC3986 URI regexp to split the URI
preg_match(self::REGEXP_URI_PARTS, $uri, $parts);
$parts += ['query' => '', 'fragment' => ''];
if (':' === $parts['scheme'] || 1 !== preg_match(self::REGEXP_URI_SCHEME, $parts['scontent'])) {
throw new SyntaxError(sprintf('The uri `%s` contains an invalid scheme', $uri));
}
if ('' === $parts['scheme'] . $parts['authority'] && 1 === preg_match(self::REGEXP_INVALID_PATH, $parts['path'])) {
throw new SyntaxError(sprintf('The uri `%s` contains an invalid path.', $uri));
}
/** @var ComponentMap $components */
$components = array_merge(self::URI_COMPONENTS, '' === $parts['authority'] ? [] : self::parseAuthority($parts['acontent']), ['path' => $parts['path'], 'scheme' => '' === $parts['scheme'] ? null : $parts['scontent'], 'query' => '' === $parts['query'] ? null : $parts['qcontent'], 'fragment' => '' === $parts['fragment'] ? null : $parts['fcontent']]);
return $components;
}
/**
* Parses the URI authority part.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2
*
* @throws SyntaxError If the port component is invalid
*
* @return AuthorityMap
*/
public static function parseAuthority(?string $authority) : array
{
$components = ['user' => null, 'pass' => null, 'host' => null, 'port' => null];
if (null === $authority) {
return $components;
}
$components['host'] = '';
if ('' === $authority) {
return $components;
}
$parts = explode('@', $authority, 2);
if (isset($parts[1])) {
[$components['user'], $components['pass']] = explode(':', $parts[0], 2) + [1 => null];
}
preg_match(self::REGEXP_HOST_PORT, $parts[1] ?? $parts[0], $matches);
$matches += ['port' => ''];
$components['port'] = self::filterPort($matches['port']);
$components['host'] = self::filterHost($matches['host']);
return $components;
}
/**
* Filter and format the port component.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*
* @throws SyntaxError if the registered name is invalid
*/
private static function filterPort(string $port) : ?int
{
return match (\true) {
'' === $port => null,
1 === preg_match('/^\\d*$/', $port) => (int) $port,
default => throw new SyntaxError(sprintf('The port `%s` is invalid', $port)),
};
}
/**
* Returns whether a hostname is valid.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*
* @throws SyntaxError if the registered name is invalid
*/
private static function filterHost(string $host) : string
{
return match (\true) {
'' === $host => '',
'[' !== $host[0] || !\str_ends_with($host, ']') => self::filterRegisteredName($host),
!self::isIpHost(substr($host, 1, -1)) => throw new SyntaxError(sprintf('Host `%s` is invalid : the IP host is malformed', $host)),
default => $host,
};
}
/**
* Returns whether the host is an IPv4 or a registered named.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*
* @throws SyntaxError if the registered name is invalid
* @throws MissingFeature if IDN support or ICU requirement are not available or met.
* @throws ConversionFailed if the submitted IDN host cannot be converted to a valid ascii form
*/
private static function filterRegisteredName(string $host) : string
{
$formattedHost = rawurldecode($host);
if (1 === preg_match(self::REGEXP_REGISTERED_NAME, $formattedHost)) {
return $host;
}
//to test IDN host non-ascii characters must be present in the host
if (1 !== preg_match(self::REGEXP_IDN_PATTERN, $formattedHost)) {
throw new SyntaxError(sprintf('Host `%s` is invalid: the host is not a valid registered name', $host));
}
Converter::toAsciiOrFail($host);
return $host;
}
/**
* Validates a IPv6/IPfuture host.
*
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
* @link https://tools.ietf.org/html/rfc6874#section-2
* @link https://tools.ietf.org/html/rfc6874#section-4
*/
private static function isIpHost(string $ipHost) : bool
{
if (\false !== filter_var($ipHost, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return \true;
}
if (1 === preg_match(self::REGEXP_IP_FUTURE, $ipHost, $matches)) {
return !\in_array($matches['version'], ['4', '6'], \true);
}
$pos = strpos($ipHost, '%');
if (\false === $pos || 1 === preg_match(self::REGEXP_INVALID_HOST_CHARS, rawurldecode(substr($ipHost, $pos)))) {
return \false;
}
$ipHost = substr($ipHost, 0, $pos);
return \false !== filter_var($ipHost, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && \str_starts_with((string) inet_pton($ipHost), self::ZONE_ID_ADDRESS_BLOCK);
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* League.Uri (http://uri.thephpleague.com/parser).
*
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @license https://github.com/thephpleague/uri-parser/blob/master/LICENSE (MIT License)
* @version 1.4.1
* @link https://uri.thephpleague.com/parser/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare (strict_types=1);
namespace WP_Ultimo\Dependencies\League\Uri;
use InvalidArgumentException;
/**
* An exception thrown on parse attempts of invalid URIs.
*
* @see https://tools.ietf.org/html/rfc3986
* @package League\Uri
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @since 0.2.0
*/
class Exception extends InvalidArgumentException
{
/**
* Returns a new Instance from an error in URI characters.
*
* @return static
*/
public static function createFromInvalidCharacters(string $uri)
{
return new static(\sprintf('The submitted uri `%s` contains invalid characters', $uri));
}
/**
* Returns a new Instance from an error in URI characters.
*
* @return static
*/
public static function createFromInvalidScheme(string $uri)
{
return new static(\sprintf('The submitted uri `%s` contains an invalid scheme', $uri));
}
/**
* Returns a new Instance from an error in Host validation.
*
* @return static
*/
public static function createFromInvalidHost(string $host)
{
return new static(\sprintf('The submitted host `%s` is invalid', $host));
}
/**
* Returns a new Instance from an error in port validation.
*
* @return static
*/
public static function createFromInvalidHostname(string $hostname)
{
return new static(\sprintf('The submitted hostname `%s` is invalid', $hostname));
}
/**
* Returns a new Instance from an error in port validation.
*
* @param string|int $port
*
* @return static
*/
public static function createFromInvalidPort($port)
{
return new static(\sprintf('The submitted port `%s` is invalid', $port));
}
/**
* Returns a new Instance from an error in Uri path component.
*
* @return static
*/
public static function createFromInvalidPath(string $uri)
{
return new static(\sprintf('The submitted uri `%s` contains an invalid path', $uri));
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* League.Uri (http://uri.thephpleague.com/parser).
*
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @license https://github.com/thephpleague/uri-parser/blob/master/LICENSE (MIT License)
* @version 1.4.1
* @link https://uri.thephpleague.com/parser/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare (strict_types=1);
namespace WP_Ultimo\Dependencies\League\Uri;
/**
* An exception thrown if the IDN support is missing or
* the ICU is not at least version 4.6.
*
* @see https://tools.ietf.org/html/rfc3986
* @package League\Uri
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @since 1.4.0
*/
class MissingIdnSupport extends Exception
{
}

View File

@ -0,0 +1,539 @@
<?php
/**
* League.Uri (http://uri.thephpleague.com/parser).
*
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @license https://github.com/thephpleague/uri-parser/blob/master/LICENSE (MIT License)
* @version 1.4.1
* @link https://uri.thephpleague.com/parser/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare (strict_types=1);
namespace WP_Ultimo\Dependencies\League\Uri;
use UnexpectedValueException;
/**
* A class to parse a URI string according to RFC3986.
*
* @see https://tools.ietf.org/html/rfc3986
* @package League\Uri
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @since 0.1.0
*/
class Parser
{
/** @deprecated 1.4.0 will be removed in the next major point release */
const INVALID_URI_CHARS = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";
/** @deprecated 1.4.0 will be removed in the next major point release */
const SCHEME_VALID_STARTING_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/** @deprecated 1.4.0 will be removed in the next major point release */
const SCHEME_VALID_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-';
/** @deprecated 1.4.0 will be removed in the next major point release */
const LABEL_VALID_STARTING_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/** @deprecated 1.4.0 will be removed in the next major point release */
const LOCAL_LINK_PREFIX = '1111111010';
const URI_COMPONENTS = ['scheme' => null, 'user' => null, 'pass' => null, 'host' => null, 'port' => null, 'path' => '', 'query' => null, 'fragment' => null];
/** @deprecated 1.4.0 will be removed in the next major point release */
const SUB_DELIMITERS = '!$&\'()*+,;=';
/**
* Returns whether a scheme is valid.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.1
*/
public function isScheme(string $scheme) : bool
{
static $pattern = '/^[a-z][a-z0-9\\+\\.\\-]*$/i';
return '' === $scheme || 1 === \preg_match($pattern, $scheme);
}
/**
* Returns whether a hostname is valid.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
public function isHost(string $host) : bool
{
return '' === $host || $this->isIpHost($host) || $this->isRegisteredName($host);
}
/**
* Validate a IPv6/IPvfuture host.
*
* @see http://tools.ietf.org/html/rfc3986#section-3.2.2
* @see http://tools.ietf.org/html/rfc6874#section-2
* @see http://tools.ietf.org/html/rfc6874#section-4
*/
private function isIpHost(string $host) : bool
{
if ('[' !== ($host[0] ?? '') || ']' !== \substr($host, -1)) {
return \false;
}
$ip = \substr($host, 1, -1);
if (\filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return \true;
}
static $ip_future = '/^
v(?<version>[A-F0-9])+\\.
(?:
(?<unreserved>[a-z0-9_~\\-\\.])|
(?<sub_delims>[!$&\'()*+,;=:]) # also include the : character
)+
$/ix';
if (1 === \preg_match($ip_future, $ip, $matches) && !\in_array($matches['version'], ['4', '6'], \true)) {
return \true;
}
if (\false === ($pos = \strpos($ip, '%'))) {
return \false;
}
static $gen_delims = '/[:\\/?#\\[\\]@ ]/';
// Also includes space.
if (1 === \preg_match($gen_delims, \rawurldecode(\substr($ip, $pos)))) {
return \false;
}
$ip = \substr($ip, 0, $pos);
if (!\filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return \false;
}
//Only the address block fe80::/10 can have a Zone ID attach to
//let's detect the link local significant 10 bits
static $address_block = "\xfe\x80";
return 0 === \strpos((string) \inet_pton($ip), $address_block);
}
/**
* Returns whether the host is an IPv4 or a registered named.
*
* @see http://tools.ietf.org/html/rfc3986#section-3.2.2
*
* @throws MissingIdnSupport if the registered name contains non-ASCII characters
* and IDN support or ICU requirement are not available or met.
*
*/
protected function isRegisteredName(string $host) : bool
{
// Note that unreserved is purposely missing . as it is used to separate labels.
static $reg_name = '/(?(DEFINE)
(?<unreserved>[a-z0-9_~\\-])
(?<sub_delims>[!$&\'()*+,;=])
(?<encoded>%[A-F0-9]{2})
(?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
)
^(?:(?&reg_name)\\.)*(?&reg_name)\\.?$/ix';
if (1 === \preg_match($reg_name, $host)) {
return \true;
}
//to test IDN host non-ascii characters must be present in the host
static $idn_pattern = '/[^\\x20-\\x7f]/';
if (1 !== \preg_match($idn_pattern, $host)) {
return \false;
}
static $idn_support = null;
$idn_support = $idn_support ?? \function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46');
// @codeCoverageIgnoreStart
// added because it is not possible in travis to disabled the ext/intl extension
// see travis issue https://github.com/travis-ci/travis-ci/issues/4701
if (!$idn_support) {
throw new MissingIdnSupport(\sprintf('the host `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $host));
}
// @codeCoverageIgnoreEnd
$ascii_host = \idn_to_ascii($host, \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46, $arr);
// @codeCoverageIgnoreStart
if (\false === $ascii_host && 0 === $arr['errors']) {
throw new UnexpectedValueException(\sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', \PHP_OS));
}
// @codeCoverageIgnoreEnd
return 0 === $arr['errors'];
}
/**
* Returns whether a port is valid.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
public function isPort($port) : bool
{
static $pattern = '/^[0-9]+$/';
if (null === $port || '' === $port) {
return \true;
}
return 1 === \preg_match($pattern, (string) $port);
}
/**
* Parse a URI string into its components.
*
* @see Parser::parse
*
* @throws Exception if the URI contains invalid characters
*/
public function __invoke(string $uri) : array
{
return $this->parse($uri);
}
/**
* Parse an URI string into its components.
*
* This method parses a URI and returns an associative array containing any
* of the various components of the URI that are present.
*
* <code>
* $components = (new Parser())->parse('http://foo@test.example.com:42?query#');
* var_export($components);
* //will display
* array(
* 'scheme' => 'http', // the URI scheme component
* 'user' => 'foo', // the URI user component
* 'pass' => null, // the URI pass component
* 'host' => 'test.example.com', // the URI host component
* 'port' => 42, // the URI port component
* 'path' => '', // the URI path component
* 'query' => 'query', // the URI query component
* 'fragment' => '', // the URI fragment component
* );
* </code>
*
* The returned array is similar to PHP's parse_url return value with the following
* differences:
*
* <ul>
* <li>All components are always present in the returned array</li>
* <li>Empty and undefined component are treated differently. And empty component is
* set to the empty string while an undefined component is set to the `null` value.</li>
* <li>The path component is never undefined</li>
* <li>The method parses the URI following the RFC3986 rules but you are still
* required to validate the returned components against its related scheme specific rules.</li>
* </ul>
*
* @see https://tools.ietf.org/html/rfc3986
* @see https://tools.ietf.org/html/rfc3986#section-2
*
* @throws Exception if the URI contains invalid characters
*/
public function parse(string $uri) : array
{
static $pattern = '/[\\x00-\\x1f\\x7f]/';
//simple URI which do not need any parsing
static $simple_uri = ['' => [], '#' => ['fragment' => ''], '?' => ['query' => ''], '?#' => ['query' => '', 'fragment' => ''], '/' => ['path' => '/'], '//' => ['host' => '']];
if (isset($simple_uri[$uri])) {
return \array_merge(self::URI_COMPONENTS, $simple_uri[$uri]);
}
if (1 === \preg_match($pattern, $uri)) {
throw Exception::createFromInvalidCharacters($uri);
}
//if the first character is a known URI delimiter parsing can be simplified
$first_char = $uri[0];
//The URI is made of the fragment only
if ('#' === $first_char) {
$components = self::URI_COMPONENTS;
$components['fragment'] = (string) \substr($uri, 1);
return $components;
}
//The URI is made of the query and fragment
if ('?' === $first_char) {
$components = self::URI_COMPONENTS;
list($components['query'], $components['fragment']) = \explode('#', \substr($uri, 1), 2) + [1 => null];
return $components;
}
//The URI does not contain any scheme part
if (0 === \strpos($uri, '//')) {
return $this->parseSchemeSpecificPart($uri);
}
//The URI is made of a path, query and fragment
if ('/' === $first_char || \false === \strpos($uri, ':')) {
return $this->parsePathQueryAndFragment($uri);
}
//Fallback parser
return $this->fallbackParser($uri);
}
/**
* Extract components from a URI without a scheme part.
*
* The URI MUST start with the authority component
* preceded by its delimiter the double slash ('//')
*
* Example: //user:pass@host:42/path?query#fragment
*
* The authority MUST adhere to the RFC3986 requirements.
*
* If the URI contains a path component, it MUST be empty or absolute
* according to RFC3986 path classification.
*
* This method returns an associative array containing all URI components.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2
* @see https://tools.ietf.org/html/rfc3986#section-3.3
*
* @throws Exception If any component of the URI is invalid
*/
protected function parseSchemeSpecificPart(string $uri) : array
{
//We remove the authority delimiter
$remaining_uri = (string) \substr($uri, 2);
$components = self::URI_COMPONENTS;
//Parsing is done from the right upmost part to the left
//1 - detect fragment, query and path part if any
list($remaining_uri, $components['fragment']) = \explode('#', $remaining_uri, 2) + [1 => null];
list($remaining_uri, $components['query']) = \explode('?', $remaining_uri, 2) + [1 => null];
if (\false !== \strpos($remaining_uri, '/')) {
list($remaining_uri, $components['path']) = \explode('/', $remaining_uri, 2) + [1 => null];
$components['path'] = '/' . $components['path'];
}
//2 - The $remaining_uri represents the authority part
//if the authority part is empty parsing is simplified
if ('' === $remaining_uri) {
$components['host'] = '';
return $components;
}
//otherwise we split the authority into the user information and the hostname parts
$parts = \explode('@', $remaining_uri, 2);
$hostname = $parts[1] ?? $parts[0];
$user_info = isset($parts[1]) ? $parts[0] : null;
if (null !== $user_info) {
list($components['user'], $components['pass']) = \explode(':', $user_info, 2) + [1 => null];
}
list($components['host'], $components['port']) = $this->parseHostname($hostname);
return $components;
}
/**
* Parse and validate the URI hostname.
*
* @throws Exception If the hostname is invalid
*/
protected function parseHostname(string $hostname) : array
{
if (\false === \strpos($hostname, '[')) {
list($host, $port) = \explode(':', $hostname, 2) + [1 => null];
return [$this->filterHost($host), $this->filterPort($port)];
}
$delimiter_offset = \strpos($hostname, ']') + 1;
if (isset($hostname[$delimiter_offset]) && ':' !== $hostname[$delimiter_offset]) {
throw Exception::createFromInvalidHostname($hostname);
}
return [$this->filterHost(\substr($hostname, 0, $delimiter_offset)), $this->filterPort(\substr($hostname, ++$delimiter_offset))];
}
/**
* validate the host component.
*
* @param string|null $host
*
* @throws Exception If the hostname is invalid
*
* @return string|null
*/
protected function filterHost($host)
{
if (null === $host || $this->isHost($host)) {
return $host;
}
throw Exception::createFromInvalidHost($host);
}
/**
* Validate a port number.
*
* An exception is raised for ports outside the established TCP and UDP port ranges.
*
* @param mixed $port the port number
*
* @throws Exception If the port number is invalid.
*
* @return null|int
*/
protected function filterPort($port)
{
static $pattern = '/^[0-9]+$/';
if (null === $port || \false === $port || '' === $port) {
return null;
}
if (1 !== \preg_match($pattern, (string) $port)) {
throw Exception::createFromInvalidPort($port);
}
return (int) $port;
}
/**
* Extract Components from an URI without scheme or authority part.
*
* The URI contains a path component and MUST adhere to path requirements
* of RFC3986. The path can be
*
* <code>
* path = path-abempty ; begins with "/" or is empty
* / path-absolute ; begins with "/" but not "//"
* / path-noscheme ; begins with a non-colon segment
* / path-rootless ; begins with a segment
* / path-empty ; zero characters
* </code>
*
* ex: path?q#f
* ex: /path
* ex: /pa:th#f
*
* This method returns an associative array containing all URI components.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.3
*
* @throws Exception If the path component is invalid
*/
protected function parsePathQueryAndFragment(string $uri) : array
{
//No scheme is present so we ensure that the path respects RFC3986
if (\false !== ($pos = \strpos($uri, ':')) && \false === \strpos(\substr($uri, 0, $pos), '/')) {
throw Exception::createFromInvalidPath($uri);
}
$components = self::URI_COMPONENTS;
//Parsing is done from the right upmost part to the left
//1 - detect the fragment part if any
list($remaining_uri, $components['fragment']) = \explode('#', $uri, 2) + [1 => null];
//2 - detect the query and the path part
list($components['path'], $components['query']) = \explode('?', $remaining_uri, 2) + [1 => null];
return $components;
}
/**
* Extract components from an URI containing a colon.
*
* Depending on the colon ":" position and on the string
* composition before the presence of the colon, the URI
* will be considered to have an scheme or not.
*
* <ul>
* <li>In case no valid scheme is found according to RFC3986 the URI will
* be parsed as an URI without a scheme and an authority</li>
* <li>In case an authority part is detected the URI specific part is parsed
* as an URI without scheme</li>
* </ul>
*
* ex: email:johndoe@thephpleague.com?subject=Hellow%20World!
*
* This method returns an associative array containing all
* the URI components.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.1
* @see Parser::parsePathQueryAndFragment
* @see Parser::parseSchemeSpecificPart
*
* @throws Exception If the URI scheme component is empty
*/
protected function fallbackParser(string $uri) : array
{
//1 - we split the URI on the first detected colon character
$parts = \explode(':', $uri, 2);
$remaining_uri = $parts[1] ?? $parts[0];
$scheme = isset($parts[1]) ? $parts[0] : null;
//1.1 - a scheme can not be empty (ie a URI can not start with a colon)
if ('' === $scheme) {
throw Exception::createFromInvalidScheme($uri);
}
//2 - depending on the scheme presence and validity we will differ the parsing
//2.1 - If the scheme part is invalid the URI may be an URI with a path-noscheme
// let's differ the parsing to the Parser::parsePathQueryAndFragment method
if (!$this->isScheme($scheme)) {
return $this->parsePathQueryAndFragment($uri);
}
$components = self::URI_COMPONENTS;
$components['scheme'] = $scheme;
//2.2 - if no scheme specific part is detect parsing is finished
if ('' == $remaining_uri) {
return $components;
}
//2.3 - if the scheme specific part is a double forward slash
if ('//' === $remaining_uri) {
$components['host'] = '';
return $components;
}
//2.4 - if the scheme specific part starts with double forward slash
// we differ the remaining parsing to the Parser::parseSchemeSpecificPart method
if (0 === \strpos($remaining_uri, '//')) {
$components = $this->parseSchemeSpecificPart($remaining_uri);
$components['scheme'] = $scheme;
return $components;
}
//2.5 - Parsing is done from the right upmost part to the left from the scheme specific part
//2.5.1 - detect the fragment part if any
list($remaining_uri, $components['fragment']) = \explode('#', $remaining_uri, 2) + [1 => null];
//2.5.2 - detect the part and query part if any
list($components['path'], $components['query']) = \explode('?', $remaining_uri, 2) + [1 => null];
return $components;
}
/**
* Convert a registered name label to its IDNA ASCII form.
*
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* @deprecated 1.4.0 this method is no longer used to validate RFC3987 compliant host component
* @codeCoverageIgnore
*
* Conversion is done only if the label contains none valid label characters
* if a '%' sub delimiter is detected the label MUST be rawurldecode prior to
* making the conversion
*
* @return string|false
*/
protected function toAscii(string $label)
{
\trigger_error(self::class . '::' . __METHOD__ . ' is deprecated and will be removed in the next major point release', \E_USER_DEPRECATED);
if (\false !== \strpos($label, '%')) {
$label = \rawurldecode($label);
}
static $idn_support = null;
$idn_support = $idn_support ?? \function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46');
if (!$idn_support) {
throw new MissingIdnSupport(\sprintf('the label `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $label));
}
$ascii_host = \idn_to_ascii($label, \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46, $arr);
if (\false === $ascii_host && 0 === $arr['errors']) {
throw new UnexpectedValueException(\sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', \PHP_OS));
}
return $ascii_host;
}
/**
* Returns whether the registered name label is valid.
*
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* @deprecated 1.4.0 this method is no longer used to validated the host component
* @codeCoverageIgnore
*
* A valid registered name label MUST conform to the following ABNF
*
* reg-name = *( unreserved / pct-encoded / sub-delims )
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
*
* @param string $label
*/
protected function isHostLabel($label) : bool
{
\trigger_error(self::class . '::' . __METHOD__ . ' is deprecated and will be removed in the next major point release', \E_USER_DEPRECATED);
return '' != $label && 63 >= \strlen($label) && \strlen($label) == \strspn($label, self::LABEL_VALID_STARTING_CHARS . '-_~' . self::SUB_DELIMITERS);
}
/**
* Validate an IPv6 host.
*
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* @deprecated 1.4.0 this method is no longer used to validated the host component
* @codeCoverageIgnore
*
* @see http://tools.ietf.org/html/rfc6874#section-2
* @see http://tools.ietf.org/html/rfc6874#section-4
*/
protected function isIpv6Host(string $ipv6) : bool
{
\trigger_error(self::class . '::' . __METHOD__ . ' is deprecated and will be removed in the next major point release', \E_USER_DEPRECATED);
if ('[' !== ($ipv6[0] ?? '') || ']' !== \substr($ipv6, -1)) {
return \false;
}
$ipv6 = \substr($ipv6, 1, -1);
if (\false === ($pos = \strpos($ipv6, '%'))) {
return (bool) \filter_var($ipv6, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
}
$scope = \rawurldecode(\substr($ipv6, $pos));
if (\strlen($scope) !== \strcspn($scope, '?#@[]')) {
return \false;
}
$ipv6 = \substr($ipv6, 0, $pos);
if (!\filter_var($ipv6, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return \false;
}
//Only the address block fe80::/10 can have a Zone ID attach to
//let's detect the link local significant 10 bits
return 0 === \strpos((string) \inet_pton($ipv6), "\xfe\x80");
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* League.Uri (http://uri.thephpleague.com/parser).
*
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @license https://github.com/thephpleague/uri-parser/blob/master/LICENSE (MIT License)
* @version 1.4.1
* @link https://uri.thephpleague.com/parser/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare (strict_types=1);
namespace WP_Ultimo\Dependencies\League\Uri;
/**
* Returns whether the URI host component is valid according to RFC3986.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
* @see Parser::isHost()
*/
function is_host(string $host) : bool
{
static $parser;
$parser = $parser ?? new Parser();
return $parser->isHost($host);
}
/**
* Returns whether the URI port component is valid according to RFC3986.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2.3
* @see Parser::isPort()
*/
function is_port($port) : bool
{
static $parser;
$parser = $parser ?? new Parser();
return $parser->isPort($port);
}
/**
* Returns whether the URI scheme component is valid according to RFC3986.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.1
* @see Parser::isScheme()
*/
function is_scheme(string $scheme) : bool
{
static $parser;
$parser = $parser ?? new Parser();
return $parser->isScheme($scheme);
}
/**
* Parse an URI string into its components.
*
* This method parses a URL and returns an associative array containing any
* of the various components of the URL that are present.
*
* @see https://tools.ietf.org/html/rfc3986
* @see https://tools.ietf.org/html/rfc3986#section-2
* @see Parser::parse()
*
* @throws Exception if the URI contains invalid characters
*/
function parse(string $uri) : array
{
static $parser;
$parser = $parser ?? new Parser();
return $parser->parse($uri);
}
/**
* Generate an URI string representation from its parsed representation
* returned by League\Uri\Parser::parse() or PHP's parse_url.
*
* If you supply your own array, you are responsible for providing
* valid components without their URI delimiters.
*
* For security reasons the password (pass) component has been deprecated
* as per RFC3986 and is never returned in the URI string
*
* @see https://tools.ietf.org/html/rfc3986#section-5.3
* @see https://tools.ietf.org/html/rfc3986#section-7.5
*/
function build(array $components) : string
{
$uri = $components['path'] ?? '';
if (isset($components['query'])) {
$uri .= '?' . $components['query'];
}
if (isset($components['fragment'])) {
$uri .= '#' . $components['fragment'];
}
if (isset($components['host'])) {
$authority = $components['host'];
if (isset($components['port'])) {
$authority .= ':' . $components['port'];
}
if (isset($components['user'])) {
$authority = $components['user'] . '@' . $authority;
}
$uri = '//' . $authority . $uri;
}
if (isset($components['scheme'])) {
return $components['scheme'] . ':' . $uri;
}
return $uri;
}

View File

@ -0,0 +1,18 @@
<?php
namespace WP_Ultimo\Dependencies;
/**
* League.Uri (http://uri.thephpleague.com/parser).
*
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @license https://github.com/thephpleague/uri-parser/blob/master/LICENSE (MIT License)
* @version 1.4.1
* @link https://uri.thephpleague.com/parser/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!\function_exists('WP_Ultimo\\Dependencies\\League\\Uri\\parse')) {
require __DIR__ . '/functions.php';
}

417
dependencies/league/uri/BaseUri.php vendored Normal file
View File

@ -0,0 +1,417 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use JsonSerializable;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriAccess;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriInterface;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\MissingFeature;
use WP_Ultimo\Dependencies\League\Uri\Idna\Converter;
use WP_Ultimo\Dependencies\League\Uri\IPv4\Converter as IPv4Converter;
use WP_Ultimo\Dependencies\Psr\Http\Message\UriFactoryInterface;
use WP_Ultimo\Dependencies\Psr\Http\Message\UriInterface as Psr7UriInterface;
use Stringable;
use function array_pop;
use function array_reduce;
use function count;
use function end;
use function explode;
use function implode;
use function str_repeat;
use function strpos;
use function substr;
/**
* @phpstan-import-type ComponentMap from UriInterface
*/
class BaseUri implements Stringable, JsonSerializable, UriAccess
{
/** @var array<string,int> */
protected final const WHATWG_SPECIAL_SCHEMES = ['ftp' => 1, 'http' => 1, 'https' => 1, 'ws' => 1, 'wss' => 1];
/** @var array<string,int> */
protected final const DOT_SEGMENTS = ['.' => 1, '..' => 1];
protected readonly Psr7UriInterface|UriInterface|null $origin;
protected readonly ?string $nullValue;
protected final 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
{
return new static(static::formatHost(static::filterUri($uri, $uriFactory)), $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),
};
}
/**
* 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 $this->normalize(static::filterUri($uri)) === $this->normalize($this->uri);
}
/**
* Tells whether the URI contains an Internationalized Domain Name (IDN).
*/
public function hasIdn() : bool
{
return Converter::isIdn($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
{
$uri = static::formatHost(static::filterUri($uri, $this->uriFactory));
$null = $uri instanceof Psr7UriInterface ? '' : null;
if ($null !== $uri->getScheme()) {
return new static($uri->withPath(static::removeDotSegments($uri->getPath())), $this->uriFactory);
}
if ($null !== $uri->getAuthority()) {
return new static($uri->withScheme($this->uri->getScheme())->withPath(static::removeDotSegments($uri->getPath())), $this->uriFactory);
}
$user = $null;
$pass = null;
$userInfo = $this->uri->getUserInfo();
if (null !== $userInfo) {
[$user, $pass] = explode(':', $userInfo, 2) + [1 => null];
}
[$path, $query] = $this->resolvePathAndQuery($uri);
return new static($uri->withPath($this->removeDotSegments($path))->withQuery($query)->withHost($this->uri->getHost())->withPort($this->uri->getPort())->withUserInfo((string) $user, $pass)->withScheme($this->uri->getScheme()), $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);
}
protected final function computeOrigin(Psr7UriInterface|UriInterface $uri, ?string $nullValue) : Psr7UriInterface|UriInterface|null
{
$scheme = $uri->getScheme();
if ('blob' !== $scheme) {
return match (\true) {
isset(static::WHATWG_SPECIAL_SCHEMES[$scheme]) => $uri->withFragment($nullValue)->withQuery($nullValue)->withPath('')->withUserInfo($nullValue),
default => null,
};
}
$components = UriString::parse($uri->getPath());
if ($uri instanceof Psr7UriInterface) {
/** @var ComponentMap $components */
$components = \array_map(fn($component) => null === $component ? '' : $component, $components);
}
return match (\true) {
null !== $components['scheme'] && isset(static::WHATWG_SPECIAL_SCHEMES[\strtolower($components['scheme'])]) => $uri->withFragment($nullValue)->withQuery($nullValue)->withPath('')->withHost($components['host'])->withPort($components['port'])->withScheme($components['scheme'])->withUserInfo($nullValue),
default => null,
};
}
/**
* Normalizes a URI for comparison; this URI string representation is not suitable for usage as per RFC guidelines.
*/
protected final function normalize(Psr7UriInterface|UriInterface $uri) : string
{
$null = $uri instanceof Psr7UriInterface ? '' : null;
$path = $uri->getPath();
if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme() . $uri->getAuthority()) {
$path = $this->removeDotSegments($path);
}
$query = $uri->getQuery();
$pairs = null === $query ? [] : explode('&', $query);
\sort($pairs);
static $regexpEncodedChars = ',%(2[D|E]|3\\d|4[1-9|A-F]|5[\\d|AF]|6[1-9|A-F]|7[\\d|E]),i';
$value = \preg_replace_callback($regexpEncodedChars, static fn(array $matches): string => \rawurldecode($matches[0]), [$path, implode('&', $pairs)]) ?? ['', $null];
[$path, $query] = $value + ['', $null];
if ($null !== $uri->getAuthority() && '' === $path) {
$path = '/';
}
return $uri->withHost(Uri::fromComponents(['host' => $uri->getHost()])->getHost())->withPath($path)->withQuery([] === $pairs ? $null : $query)->withFragment($null)->__toString();
}
/**
* Input URI normalization to allow Stringable and string URI.
*/
protected static final 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),
};
}
/**
* Remove dot segments from the URI path as per RFC specification.
*/
protected final 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[end($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}
*/
protected final 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()];
}
/**
* Tells whether the component value from both URI object equals.
*
* @pqram 'query'|'authority'|'scheme' $property
*/
protected final 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.
*/
protected static final function formatHost(Psr7UriInterface|UriInterface $uri) : Psr7UriInterface|UriInterface
{
$host = $uri->getHost();
try {
$converted = IPv4Converter::fromEnvironment()->toDecimal($host);
} catch (MissingFeature) {
$converted = null;
}
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.
*/
protected final 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.
*/
protected static final 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[]
*/
protected static final 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.
*/
protected static final 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.
*/
protected static final function formatPathWithEmptyBaseQuery(string $path) : string
{
$targetSegments = static::getSegments($path);
/** @var string $basename */
$basename = end($targetSegments);
return '' === $basename ? './' : $basename;
}
}

237
dependencies/league/uri/Http.php vendored Normal file
View File

@ -0,0 +1,237 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use JsonSerializable;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriException;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriInterface;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use WP_Ultimo\Dependencies\League\Uri\UriTemplate\TemplateCanNotBeExpanded;
use WP_Ultimo\Dependencies\Psr\Http\Message\UriInterface as Psr7UriInterface;
use Stringable;
/**
* @phpstan-import-type InputComponentMap from UriString
*/
final class Http implements Stringable, Psr7UriInterface, JsonSerializable
{
private function __construct(private readonly UriInterface $uri)
{
if (null === $this->uri->getScheme() && '' === $this->uri->getHost()) {
throw new SyntaxError('An URI without scheme cannot contain an empty host string according to PSR-7: ' . $uri);
}
$port = $this->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);
}
}
/**
* Create a new instance from a string or a stringable object.
*/
public static function new(Stringable|string $uri = '') : self
{
return match (\true) {
$uri instanceof UriInterface => new self($uri),
default => new self(Uri::new($uri)),
};
}
/**
* 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
{
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));
}
/**
* Create a new instance from a URI and a Base URI.
*
* The returned URI must be absolute.
*/
public static function fromBaseUri(Stringable|string $uri, Stringable|string|null $baseUri = null) : self
{
return new self(Uri::fromBaseUri($uri, $baseUri));
}
/**
* 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));
}
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
{
return $this->uri->getPath();
}
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 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.0.0
* @codeCoverageIgnore
* @see Http::new()
*
* Create a new instance from a string.
*/
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
*/
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.
*/
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.
*/
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.
*/
public static function createFromBaseUri(Stringable|string $uri, Stringable|string|null $baseUri = null) : self
{
return self::fromBaseUri($uri, $baseUri);
}
}

22
dependencies/league/uri/HttpFactory.php vendored Normal file
View File

@ -0,0 +1,22 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use WP_Ultimo\Dependencies\Psr\Http\Message\UriFactoryInterface;
use WP_Ultimo\Dependencies\Psr\Http\Message\UriInterface;
final class HttpFactory implements UriFactoryInterface
{
public function createUri(string $uri = '') : UriInterface
{
return Http::new($uri);
}
}

1028
dependencies/league/uri/Uri.php vendored Normal file

File diff suppressed because it is too large Load Diff

87
dependencies/league/uri/UriInfo.php vendored Normal file
View File

@ -0,0 +1,87 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriInterface;
use WP_Ultimo\Dependencies\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.
*/
public static function isAbsolute(Psr7UriInterface|UriInterface $uri) : bool
{
return BaseUri::from($uri)->isAbsolute();
}
/**
* Tell whether the URI represents a network path.
*/
public static function isNetworkPath(Psr7UriInterface|UriInterface $uri) : bool
{
return BaseUri::from($uri)->isNetworkPath();
}
/**
* Tells whether the URI represents an absolute path.
*/
public static function isAbsolutePath(Psr7UriInterface|UriInterface $uri) : bool
{
return BaseUri::from($uri)->isAbsolutePath();
}
/**
* Tell whether the URI represents a relative path.
*
*/
public static function isRelativePath(Psr7UriInterface|UriInterface $uri) : bool
{
return BaseUri::from($uri)->isRelativePath();
}
/**
* Tells whether both URI refers to the same document.
*/
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)
*/
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()
*/
public static function isCrossOrigin(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $baseUri) : bool
{
return BaseUri::from($baseUri)->isCrossOrigin($uri);
}
}

49
dependencies/league/uri/UriResolver.php vendored Normal file
View File

@ -0,0 +1,49 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriInterface;
use WP_Ultimo\Dependencies\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.
*/
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.
*/
public static function relativize(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $baseUri) : Psr7UriInterface|UriInterface
{
return BaseUri::from($baseUri)->relativize($uri)->getUri();
}
}

101
dependencies/league/uri/UriTemplate.php vendored Normal file
View File

@ -0,0 +1,101 @@
<?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 WP_Ultimo\Dependencies\League\Uri;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriException;
use WP_Ultimo\Dependencies\League\Uri\Contracts\UriInterface;
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
use WP_Ultimo\Dependencies\League\Uri\UriTemplate\Template;
use WP_Ultimo\Dependencies\League\Uri\UriTemplate\TemplateCanNotBeExpanded;
use WP_Ultimo\Dependencies\League\Uri\UriTemplate\VariableBag;
use Stringable;
use function array_fill_keys;
use function array_key_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
*/
final class UriTemplate
{
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(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)));
}
public function getTemplate() : string
{
return $this->template->value;
}
/**
* @return array<string>
*/
public function getVariableNames() : array
{
return $this->template->variableNames;
}
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 ($defaultVariables == $this->defaultVariables) {
return $this;
}
return new self($this->template, $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 = []) : UriInterface
{
return Uri::new($this->template->expand($this->filterVariables($variables)->replace($this->defaultVariables)));
}
/**
* @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 = []) : UriInterface
{
return Uri::new($this->template->expandOrFail($this->filterVariables($variables)->replace($this->defaultVariables)));
}
}

View File

@ -0,0 +1,67 @@
<?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 WP_Ultimo\Dependencies\League\Uri\UriTemplate;
use WP_Ultimo\Dependencies\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
*/
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,
};
}
}

View File

@ -0,0 +1,193 @@
<?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 WP_Ultimo\Dependencies\League\Uri\UriTemplate;
use WP_Ultimo\Dependencies\League\Uri\Encoder;
use WP_Ultimo\Dependencies\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];
}
}

View File

@ -0,0 +1,111 @@
<?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 WP_Ultimo\Dependencies\League\Uri\UriTemplate;
use WP_Ultimo\Dependencies\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_contains;
use function str_replace;
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(Stringable|string $template) : self
{
$template = (string) $template;
/** @var string $remainder */
$remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
if (str_contains($remainder, '{') || str_contains($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.
*
*/
public static function createFromString(Stringable|string $template) : self
{
return self::new($template);
}
}

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 WP_Ultimo\Dependencies\League\Uri\UriTemplate;
use InvalidArgumentException;
use WP_Ultimo\Dependencies\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,57 @@
<?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 WP_Ultimo\Dependencies\League\Uri\UriTemplate;
use WP_Ultimo\Dependencies\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 = 10000;
private function __construct(public readonly string $name, public readonly string $modifier, public readonly int $position)
{
}
public static function new(string $specification) : self
{
if (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 => '',
};
}
}

View File

@ -0,0 +1,130 @@
<?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 WP_Ultimo\Dependencies\League\Uri\UriTemplate;
use ArrayAccess;
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;
}
/**
* 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, 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(Stringable|string|float|int|bool|array|null $value, string $name, bool $isNestedListAllowed) : array|string
{
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));
}
}