Server IP : 213.176.29.180  /  Your IP : 18.221.25.133
Web Server : Apache
System : Linux 213.176.29.180.hostiran.name 4.18.0-553.22.1.el8_10.x86_64 #1 SMP Tue Sep 24 05:16:59 EDT 2024 x86_64
User : webtaragh ( 1001)
PHP Version : 8.3.14
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON
Directory (0750) :  /home/webtaragh/public_html/

[  Home  ][  C0mmand  ][  Upload File  ]

Current File : /home/webtaragh/public_html/league.tar
oauth2-client/CONTRIBUTING.md000064400000003556147361032630011471 0ustar00# Contributing

Contributions are **welcome** and will be fully **credited**.

We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-client).


## Pull Requests

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option.

- **Create topic branches** - Don't ask us to pull from your master branch.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.

- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass.

- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails.


## Testing

The following tests must pass for a build to be considered successful. If contributing, please ensure these pass before submitting a pull request.

``` bash
$ ./vendor/bin/parallel-lint src test
$ ./vendor/bin/phpunit --coverage-text
$ ./vendor/bin/phpcs src --standard=psr2 -sp
```

**Happy coding**!
oauth2-client/CREDITS.md000064400000001073147361032630010647 0ustar00# OAuth 2.0 Client

## Authors

Also see <https://github.com/thephpleague/oauth2-client/contributors>.

### Current Maintainer

- [Ben Ramsey](https://github.com/ramsey)

### Contributors

- [Alex Bilbie](https://github.com/alexbilbie)
- [Ben Corlett](https://github.com/bencorlett)
- [Ben Ramsey](https://github.com/ramsey)
- [James Mills](https://github.com/jamesmills)
- [Phil Sturgeon](https://github.com/philsturgeon)
- [Rudi Theunissen](https://github.com/rtheunissen)
- [Tom Anderson](https://github.com/TomHAnderson)
- [Woody Gilk](https://github.com/shadowhand)
oauth2-client/CODE_OF_CONDUCT.md000064400000006444147361032630012036 0ustar00# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
 advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
 address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
 professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at https://twitter.com/thephpleague. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
oauth2-client/src/Provider/AbstractProvider.php000064400000056325147361032630015612 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Provider;

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\ClientInterface as HttpClientInterface;
use GuzzleHttp\Exception\BadResponseException;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Grant\GrantFactory;
use League\OAuth2\Client\OptionProvider\OptionProviderInterface;
use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Token\AccessTokenInterface;
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
use League\OAuth2\Client\Tool\GuardedPropertyTrait;
use League\OAuth2\Client\Tool\QueryBuilderTrait;
use League\OAuth2\Client\Tool\RequestFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use UnexpectedValueException;

/**
 * Represents a service provider (authorization server).
 *
 * @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, §1.1)
 */
abstract class AbstractProvider
{
    use ArrayAccessorTrait;
    use GuardedPropertyTrait;
    use QueryBuilderTrait;

    /**
     * @var string Key used in a token response to identify the resource owner.
     */
    const ACCESS_TOKEN_RESOURCE_OWNER_ID = null;

    /**
     * @var string HTTP method used to fetch access tokens.
     */
    const METHOD_GET = 'GET';

    /**
     * @var string HTTP method used to fetch access tokens.
     */
    const METHOD_POST = 'POST';

    /**
     * @var string
     */
    protected $clientId;

    /**
     * @var string
     */
    protected $clientSecret;

    /**
     * @var string
     */
    protected $redirectUri;

    /**
     * @var string
     */
    protected $state;

    /**
     * @var GrantFactory
     */
    protected $grantFactory;

    /**
     * @var RequestFactory
     */
    protected $requestFactory;

    /**
     * @var HttpClientInterface
     */
    protected $httpClient;

    /**
     * @var OptionProviderInterface
     */
    protected $optionProvider;

    /**
     * Constructs an OAuth 2.0 service provider.
     *
     * @param array $options An array of options to set on this provider.
     *     Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
     *     Individual providers may introduce more options, as needed.
     * @param array $collaborators An array of collaborators that may be used to
     *     override this provider's default behavior. Collaborators include
     *     `grantFactory`, `requestFactory`, and `httpClient`.
     *     Individual providers may introduce more collaborators, as needed.
     */
    public function __construct(array $options = [], array $collaborators = [])
    {
        // We'll let the GuardedPropertyTrait handle mass assignment of incoming
        // options, skipping any blacklisted properties defined in the provider
        $this->fillProperties($options);

        if (empty($collaborators['grantFactory'])) {
            $collaborators['grantFactory'] = new GrantFactory();
        }
        $this->setGrantFactory($collaborators['grantFactory']);

        if (empty($collaborators['requestFactory'])) {
            $collaborators['requestFactory'] = new RequestFactory();
        }
        $this->setRequestFactory($collaborators['requestFactory']);

        if (empty($collaborators['httpClient'])) {
            $client_options = $this->getAllowedClientOptions($options);

            $collaborators['httpClient'] = new HttpClient(
                array_intersect_key($options, array_flip($client_options))
            );
        }
        $this->setHttpClient($collaborators['httpClient']);

        if (empty($collaborators['optionProvider'])) {
            $collaborators['optionProvider'] = new PostAuthOptionProvider();
        }
        $this->setOptionProvider($collaborators['optionProvider']);
    }

    /**
     * Returns the list of options that can be passed to the HttpClient
     *
     * @param array $options An array of options to set on this provider.
     *     Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
     *     Individual providers may introduce more options, as needed.
     * @return array The options to pass to the HttpClient constructor
     */
    protected function getAllowedClientOptions(array $options)
    {
        $client_options = ['timeout', 'proxy'];

        // Only allow turning off ssl verification if it's for a proxy
        if (!empty($options['proxy'])) {
            $client_options[] = 'verify';
        }

        return $client_options;
    }

    /**
     * Sets the grant factory instance.
     *
     * @param  GrantFactory $factory
     * @return self
     */
    public function setGrantFactory(GrantFactory $factory)
    {
        $this->grantFactory = $factory;

        return $this;
    }

    /**
     * Returns the current grant factory instance.
     *
     * @return GrantFactory
     */
    public function getGrantFactory()
    {
        return $this->grantFactory;
    }

    /**
     * Sets the request factory instance.
     *
     * @param  RequestFactory $factory
     * @return self
     */
    public function setRequestFactory(RequestFactory $factory)
    {
        $this->requestFactory = $factory;

        return $this;
    }

    /**
     * Returns the request factory instance.
     *
     * @return RequestFactory
     */
    public function getRequestFactory()
    {
        return $this->requestFactory;
    }

    /**
     * Sets the HTTP client instance.
     *
     * @param  HttpClientInterface $client
     * @return self
     */
    public function setHttpClient(HttpClientInterface $client)
    {
        $this->httpClient = $client;

        return $this;
    }

    /**
     * Returns the HTTP client instance.
     *
     * @return HttpClientInterface
     */
    public function getHttpClient()
    {
        return $this->httpClient;
    }

    /**
     * Sets the option provider instance.
     *
     * @param  OptionProviderInterface $provider
     * @return self
     */
    public function setOptionProvider(OptionProviderInterface $provider)
    {
        $this->optionProvider = $provider;

        return $this;
    }

    /**
     * Returns the option provider instance.
     *
     * @return OptionProviderInterface
     */
    public function getOptionProvider()
    {
        return $this->optionProvider;
    }

    /**
     * Returns the current value of the state parameter.
     *
     * This can be accessed by the redirect handler during authorization.
     *
     * @return string
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * Returns the base URL for authorizing a client.
     *
     * Eg. https://oauth.service.com/authorize
     *
     * @return string
     */
    abstract public function getBaseAuthorizationUrl();

    /**
     * Returns the base URL for requesting an access token.
     *
     * Eg. https://oauth.service.com/token
     *
     * @param array $params
     * @return string
     */
    abstract public function getBaseAccessTokenUrl(array $params);

    /**
     * Returns the URL for requesting the resource owner's details.
     *
     * @param AccessToken $token
     * @return string
     */
    abstract public function getResourceOwnerDetailsUrl(AccessToken $token);

    /**
     * Returns a new random string to use as the state parameter in an
     * authorization flow.
     *
     * @param  int $length Length of the random string to be generated.
     * @return string
     */
    protected function getRandomState($length = 32)
    {
        // Converting bytes to hex will always double length. Hence, we can reduce
        // the amount of bytes by half to produce the correct length.
        return bin2hex(random_bytes($length / 2));
    }

    /**
     * Returns the default scopes used by this provider.
     *
     * This should only be the scopes that are required to request the details
     * of the resource owner, rather than all the available scopes.
     *
     * @return array
     */
    abstract protected function getDefaultScopes();

    /**
     * Returns the string that should be used to separate scopes when building
     * the URL for requesting an access token.
     *
     * @return string Scope separator, defaults to ','
     */
    protected function getScopeSeparator()
    {
        return ',';
    }

    /**
     * Returns authorization parameters based on provided options.
     *
     * @param  array $options
     * @return array Authorization parameters
     */
    protected function getAuthorizationParameters(array $options)
    {
        if (empty($options['state'])) {
            $options['state'] = $this->getRandomState();
        }

        if (empty($options['scope'])) {
            $options['scope'] = $this->getDefaultScopes();
        }

        $options += [
            'response_type'   => 'code',
            'approval_prompt' => 'auto'
        ];

        if (is_array($options['scope'])) {
            $separator = $this->getScopeSeparator();
            $options['scope'] = implode($separator, $options['scope']);
        }

        // Store the state as it may need to be accessed later on.
        $this->state = $options['state'];

        // Business code layer might set a different redirect_uri parameter
        // depending on the context, leave it as-is
        if (!isset($options['redirect_uri'])) {
            $options['redirect_uri'] = $this->redirectUri;
        }

        $options['client_id'] = $this->clientId;

        return $options;
    }

    /**
     * Builds the authorization URL's query string.
     *
     * @param  array $params Query parameters
     * @return string Query string
     */
    protected function getAuthorizationQuery(array $params)
    {
        return $this->buildQueryString($params);
    }

    /**
     * Builds the authorization URL.
     *
     * @param  array $options
     * @return string Authorization URL
     */
    public function getAuthorizationUrl(array $options = [])
    {
        $base   = $this->getBaseAuthorizationUrl();
        $params = $this->getAuthorizationParameters($options);
        $query  = $this->getAuthorizationQuery($params);

        return $this->appendQuery($base, $query);
    }

    /**
     * Redirects the client for authorization.
     *
     * @param  array $options
     * @param  callable|null $redirectHandler
     * @return mixed
     */
    public function authorize(
        array $options = [],
        callable $redirectHandler = null
    ) {
        $url = $this->getAuthorizationUrl($options);
        if ($redirectHandler) {
            return $redirectHandler($url, $this);
        }

        // @codeCoverageIgnoreStart
        header('Location: ' . $url);
        exit;
        // @codeCoverageIgnoreEnd
    }

    /**
     * Appends a query string to a URL.
     *
     * @param  string $url The URL to append the query to
     * @param  string $query The HTTP query string
     * @return string The resulting URL
     */
    protected function appendQuery($url, $query)
    {
        $query = trim($query, '?&');

        if ($query) {
            $glue = strstr($url, '?') === false ? '?' : '&';
            return $url . $glue . $query;
        }

        return $url;
    }

    /**
     * Returns the method to use when requesting an access token.
     *
     * @return string HTTP method
     */
    protected function getAccessTokenMethod()
    {
        return self::METHOD_POST;
    }

    /**
     * Returns the key used in the access token response to identify the resource owner.
     *
     * @return string|null Resource owner identifier key
     */
    protected function getAccessTokenResourceOwnerId()
    {
        return static::ACCESS_TOKEN_RESOURCE_OWNER_ID;
    }

    /**
     * Builds the access token URL's query string.
     *
     * @param  array $params Query parameters
     * @return string Query string
     */
    protected function getAccessTokenQuery(array $params)
    {
        return $this->buildQueryString($params);
    }

    /**
     * Checks that a provided grant is valid, or attempts to produce one if the
     * provided grant is a string.
     *
     * @param  AbstractGrant|string $grant
     * @return AbstractGrant
     */
    protected function verifyGrant($grant)
    {
        if (is_string($grant)) {
            return $this->grantFactory->getGrant($grant);
        }

        $this->grantFactory->checkGrant($grant);
        return $grant;
    }

    /**
     * Returns the full URL to use when requesting an access token.
     *
     * @param array $params Query parameters
     * @return string
     */
    protected function getAccessTokenUrl(array $params)
    {
        $url = $this->getBaseAccessTokenUrl($params);

        if ($this->getAccessTokenMethod() === self::METHOD_GET) {
            $query = $this->getAccessTokenQuery($params);
            return $this->appendQuery($url, $query);
        }

        return $url;
    }

    /**
     * Returns a prepared request for requesting an access token.
     *
     * @param array $params Query string parameters
     * @return RequestInterface
     */
    protected function getAccessTokenRequest(array $params)
    {
        $method  = $this->getAccessTokenMethod();
        $url     = $this->getAccessTokenUrl($params);
        $options = $this->optionProvider->getAccessTokenOptions($this->getAccessTokenMethod(), $params);

        return $this->getRequest($method, $url, $options);
    }

    /**
     * Requests an access token using a specified grant and option set.
     *
     * @param  mixed $grant
     * @param  array $options
     * @throws IdentityProviderException
     * @return AccessTokenInterface
     */
    public function getAccessToken($grant, array $options = [])
    {
        $grant = $this->verifyGrant($grant);

        $params = [
            'client_id'     => $this->clientId,
            'client_secret' => $this->clientSecret,
            'redirect_uri'  => $this->redirectUri,
        ];

        $params   = $grant->prepareRequestParameters($params, $options);
        $request  = $this->getAccessTokenRequest($params);
        $response = $this->getParsedResponse($request);
        if (false === is_array($response)) {
            throw new UnexpectedValueException(
                'Invalid response received from Authorization Server. Expected JSON.'
            );
        }
        $prepared = $this->prepareAccessTokenResponse($response);
        $token    = $this->createAccessToken($prepared, $grant);

        return $token;
    }

    /**
     * Returns a PSR-7 request instance that is not authenticated.
     *
     * @param  string $method
     * @param  string $url
     * @param  array $options
     * @return RequestInterface
     */
    public function getRequest($method, $url, array $options = [])
    {
        return $this->createRequest($method, $url, null, $options);
    }

    /**
     * Returns an authenticated PSR-7 request instance.
     *
     * @param  string $method
     * @param  string $url
     * @param  AccessTokenInterface|string $token
     * @param  array $options Any of "headers", "body", and "protocolVersion".
     * @return RequestInterface
     */
    public function getAuthenticatedRequest($method, $url, $token, array $options = [])
    {
        return $this->createRequest($method, $url, $token, $options);
    }

    /**
     * Creates a PSR-7 request instance.
     *
     * @param  string $method
     * @param  string $url
     * @param  AccessTokenInterface|string|null $token
     * @param  array $options
     * @return RequestInterface
     */
    protected function createRequest($method, $url, $token, array $options)
    {
        $defaults = [
            'headers' => $this->getHeaders($token),
        ];

        $options = array_merge_recursive($defaults, $options);
        $factory = $this->getRequestFactory();

        return $factory->getRequestWithOptions($method, $url, $options);
    }

    /**
     * Sends a request instance and returns a response instance.
     *
     * WARNING: This method does not attempt to catch exceptions caused by HTTP
     * errors! It is recommended to wrap this method in a try/catch block.
     *
     * @param  RequestInterface $request
     * @return ResponseInterface
     */
    public function getResponse(RequestInterface $request)
    {
        return $this->getHttpClient()->send($request);
    }

    /**
     * Sends a request and returns the parsed response.
     *
     * @param  RequestInterface $request
     * @throws IdentityProviderException
     * @return mixed
     */
    public function getParsedResponse(RequestInterface $request)
    {
        try {
            $response = $this->getResponse($request);
        } catch (BadResponseException $e) {
            $response = $e->getResponse();
        }

        $parsed = $this->parseResponse($response);

        $this->checkResponse($response, $parsed);

        return $parsed;
    }

    /**
     * Attempts to parse a JSON response.
     *
     * @param  string $content JSON content from response body
     * @return array Parsed JSON data
     * @throws UnexpectedValueException if the content could not be parsed
     */
    protected function parseJson($content)
    {
        $content = json_decode($content, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new UnexpectedValueException(sprintf(
                "Failed to parse JSON response: %s",
                json_last_error_msg()
            ));
        }

        return $content;
    }

    /**
     * Returns the content type header of a response.
     *
     * @param  ResponseInterface $response
     * @return string Semi-colon separated join of content-type headers.
     */
    protected function getContentType(ResponseInterface $response)
    {
        return join(';', (array) $response->getHeader('content-type'));
    }

    /**
     * Parses the response according to its content-type header.
     *
     * @throws UnexpectedValueException
     * @param  ResponseInterface $response
     * @return array
     */
    protected function parseResponse(ResponseInterface $response)
    {
        $content = (string) $response->getBody();
        $type = $this->getContentType($response);

        if (strpos($type, 'urlencoded') !== false) {
            parse_str($content, $parsed);
            return $parsed;
        }

        // Attempt to parse the string as JSON regardless of content type,
        // since some providers use non-standard content types. Only throw an
        // exception if the JSON could not be parsed when it was expected to.
        try {
            return $this->parseJson($content);
        } catch (UnexpectedValueException $e) {
            if (strpos($type, 'json') !== false) {
                throw $e;
            }

            if ($response->getStatusCode() == 500) {
                throw new UnexpectedValueException(
                    'An OAuth server error was encountered that did not contain a JSON body',
                    0,
                    $e
                );
            }

            return $content;
        }
    }

    /**
     * Checks a provider response for errors.
     *
     * @throws IdentityProviderException
     * @param  ResponseInterface $response
     * @param  array|string $data Parsed response data
     * @return void
     */
    abstract protected function checkResponse(ResponseInterface $response, $data);

    /**
     * Prepares an parsed access token response for a grant.
     *
     * Custom mapping of expiration, etc should be done here. Always call the
     * parent method when overloading this method.
     *
     * @param  mixed $result
     * @return array
     */
    protected function prepareAccessTokenResponse(array $result)
    {
        if ($this->getAccessTokenResourceOwnerId() !== null) {
            $result['resource_owner_id'] = $this->getValueByKey(
                $result,
                $this->getAccessTokenResourceOwnerId()
            );
        }
        return $result;
    }

    /**
     * Creates an access token from a response.
     *
     * The grant that was used to fetch the response can be used to provide
     * additional context.
     *
     * @param  array $response
     * @param  AbstractGrant $grant
     * @return AccessTokenInterface
     */
    protected function createAccessToken(array $response, AbstractGrant $grant)
    {
        return new AccessToken($response);
    }

    /**
     * Generates a resource owner object from a successful resource owner
     * details request.
     *
     * @param  array $response
     * @param  AccessToken $token
     * @return ResourceOwnerInterface
     */
    abstract protected function createResourceOwner(array $response, AccessToken $token);

    /**
     * Requests and returns the resource owner of given access token.
     *
     * @param  AccessToken $token
     * @return ResourceOwnerInterface
     */
    public function getResourceOwner(AccessToken $token)
    {
        $response = $this->fetchResourceOwnerDetails($token);

        return $this->createResourceOwner($response, $token);
    }

    /**
     * Requests resource owner details.
     *
     * @param  AccessToken $token
     * @return mixed
     */
    protected function fetchResourceOwnerDetails(AccessToken $token)
    {
        $url = $this->getResourceOwnerDetailsUrl($token);

        $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token);

        $response = $this->getParsedResponse($request);

        if (false === is_array($response)) {
            throw new UnexpectedValueException(
                'Invalid response received from Authorization Server. Expected JSON.'
            );
        }

        return $response;
    }

    /**
     * Returns the default headers used by this provider.
     *
     * Typically this is used to set 'Accept' or 'Content-Type' headers.
     *
     * @return array
     */
    protected function getDefaultHeaders()
    {
        return [];
    }

    /**
     * Returns the authorization headers used by this provider.
     *
     * Typically this is "Bearer" or "MAC". For more information see:
     * http://tools.ietf.org/html/rfc6749#section-7.1
     *
     * No default is provided, providers must overload this method to activate
     * authorization headers.
     *
     * @param  mixed|null $token Either a string or an access token instance
     * @return array
     */
    protected function getAuthorizationHeaders($token = null)
    {
        return [];
    }

    /**
     * Returns all headers used by this provider for a request.
     *
     * The request will be authenticated if an access token is provided.
     *
     * @param  mixed|null $token object or string
     * @return array
     */
    public function getHeaders($token = null)
    {
        if ($token) {
            return array_merge(
                $this->getDefaultHeaders(),
                $this->getAuthorizationHeaders($token)
            );
        }

        return $this->getDefaultHeaders();
    }
}
oauth2-client/src/Provider/GenericResourceOwner.php000064400000002655147361032630016430 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Provider;

/**
 * Represents a generic resource owner for use with the GenericProvider.
 */
class GenericResourceOwner implements ResourceOwnerInterface
{
    /**
     * @var array
     */
    protected $response;

    /**
     * @var string
     */
    protected $resourceOwnerId;

    /**
     * @param array $response
     * @param string $resourceOwnerId
     */
    public function __construct(array $response, $resourceOwnerId)
    {
        $this->response = $response;
        $this->resourceOwnerId = $resourceOwnerId;
    }

    /**
     * Returns the identifier of the authorized resource owner.
     *
     * @return mixed
     */
    public function getId()
    {
        return $this->response[$this->resourceOwnerId];
    }

    /**
     * Returns the raw resource owner response.
     *
     * @return array
     */
    public function toArray()
    {
        return $this->response;
    }
}
oauth2-client/src/Provider/ResourceOwnerInterface.php000064400000001763147361032630016753 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Provider;

/**
 * Classes implementing `ResourceOwnerInterface` may be used to represent
 * the resource owner authenticated with a service provider.
 */
interface ResourceOwnerInterface
{
    /**
     * Returns the identifier of the authorized resource owner.
     *
     * @return mixed
     */
    public function getId();

    /**
     * Return all of the owner details available as an array.
     *
     * @return array
     */
    public function toArray();
}
oauth2-client/src/Provider/GenericProvider.php000064400000012565147361032630015421 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Provider;

use InvalidArgumentException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;

/**
 * Represents a generic service provider that may be used to interact with any
 * OAuth 2.0 service provider, using Bearer token authentication.
 */
class GenericProvider extends AbstractProvider
{
    use BearerAuthorizationTrait;

    /**
     * @var string
     */
    private $urlAuthorize;

    /**
     * @var string
     */
    private $urlAccessToken;

    /**
     * @var string
     */
    private $urlResourceOwnerDetails;

    /**
     * @var string
     */
    private $accessTokenMethod;

    /**
     * @var string
     */
    private $accessTokenResourceOwnerId;

    /**
     * @var array|null
     */
    private $scopes = null;

    /**
     * @var string
     */
    private $scopeSeparator;

    /**
     * @var string
     */
    private $responseError = 'error';

    /**
     * @var string
     */
    private $responseCode;

    /**
     * @var string
     */
    private $responseResourceOwnerId = 'id';

    /**
     * @param array $options
     * @param array $collaborators
     */
    public function __construct(array $options = [], array $collaborators = [])
    {
        $this->assertRequiredOptions($options);

        $possible   = $this->getConfigurableOptions();
        $configured = array_intersect_key($options, array_flip($possible));

        foreach ($configured as $key => $value) {
            $this->$key = $value;
        }

        // Remove all options that are only used locally
        $options = array_diff_key($options, $configured);

        parent::__construct($options, $collaborators);
    }

    /**
     * Returns all options that can be configured.
     *
     * @return array
     */
    protected function getConfigurableOptions()
    {
        return array_merge($this->getRequiredOptions(), [
            'accessTokenMethod',
            'accessTokenResourceOwnerId',
            'scopeSeparator',
            'responseError',
            'responseCode',
            'responseResourceOwnerId',
            'scopes',
        ]);
    }

    /**
     * Returns all options that are required.
     *
     * @return array
     */
    protected function getRequiredOptions()
    {
        return [
            'urlAuthorize',
            'urlAccessToken',
            'urlResourceOwnerDetails',
        ];
    }

    /**
     * Verifies that all required options have been passed.
     *
     * @param  array $options
     * @return void
     * @throws InvalidArgumentException
     */
    private function assertRequiredOptions(array $options)
    {
        $missing = array_diff_key(array_flip($this->getRequiredOptions()), $options);

        if (!empty($missing)) {
            throw new InvalidArgumentException(
                'Required options not defined: ' . implode(', ', array_keys($missing))
            );
        }
    }

    /**
     * @inheritdoc
     */
    public function getBaseAuthorizationUrl()
    {
        return $this->urlAuthorize;
    }

    /**
     * @inheritdoc
     */
    public function getBaseAccessTokenUrl(array $params)
    {
        return $this->urlAccessToken;
    }

    /**
     * @inheritdoc
     */
    public function getResourceOwnerDetailsUrl(AccessToken $token)
    {
        return $this->urlResourceOwnerDetails;
    }

    /**
     * @inheritdoc
     */
    public function getDefaultScopes()
    {
        return $this->scopes;
    }

    /**
     * @inheritdoc
     */
    protected function getAccessTokenMethod()
    {
        return $this->accessTokenMethod ?: parent::getAccessTokenMethod();
    }

    /**
     * @inheritdoc
     */
    protected function getAccessTokenResourceOwnerId()
    {
        return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId();
    }

    /**
     * @inheritdoc
     */
    protected function getScopeSeparator()
    {
        return $this->scopeSeparator ?: parent::getScopeSeparator();
    }

    /**
     * @inheritdoc
     */
    protected function checkResponse(ResponseInterface $response, $data)
    {
        if (!empty($data[$this->responseError])) {
            $error = $data[$this->responseError];
            if (!is_string($error)) {
                $error = var_export($error, true);
            }
            $code  = $this->responseCode && !empty($data[$this->responseCode])? $data[$this->responseCode] : 0;
            if (!is_int($code)) {
                $code = intval($code);
            }
            throw new IdentityProviderException($error, $code, $data);
        }
    }

    /**
     * @inheritdoc
     */
    protected function createResourceOwner(array $response, AccessToken $token)
    {
        return new GenericResourceOwner($response, $this->responseResourceOwnerId);
    }
}
oauth2-client/src/Provider/Exception/IdentityProviderException.php000064400000002274147361032630021447 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Provider\Exception;

/**
 * Exception thrown if the provider response contains errors.
 */
class IdentityProviderException extends \Exception
{
    /**
     * @var mixed
     */
    protected $response;

    /**
     * @param string $message
     * @param int $code
     * @param array|string $response The response body
     */
    public function __construct($message, $code, $response)
    {
        $this->response = $response;

        parent::__construct($message, $code);
    }

    /**
     * Returns the exception's response body.
     *
     * @return array|string
     */
    public function getResponseBody()
    {
        return $this->response;
    }
}
oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php000064400000001414147361032630020355 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Token;

interface ResourceOwnerAccessTokenInterface extends AccessTokenInterface
{
    /**
     * Returns the resource owner identifier, if defined.
     *
     * @return string|null
     */
    public function getResourceOwnerId();
}
oauth2-client/src/Token/AccessToken.php000064400000012306147361032630014013 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Token;

use InvalidArgumentException;
use RuntimeException;

/**
 * Represents an access token.
 *
 * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4)
 */
class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface
{
    /**
     * @var string
     */
    protected $accessToken;

    /**
     * @var int
     */
    protected $expires;

    /**
     * @var string
     */
    protected $refreshToken;

    /**
     * @var string
     */
    protected $resourceOwnerId;

    /**
     * @var array
     */
    protected $values = [];

    /**
     * Constructs an access token.
     *
     * @param array $options An array of options returned by the service provider
     *     in the access token request. The `access_token` option is required.
     * @throws InvalidArgumentException if `access_token` is not provided in `$options`.
     */
    public function __construct(array $options = [])
    {
        if (empty($options['access_token'])) {
            throw new InvalidArgumentException('Required option not passed: "access_token"');
        }

        $this->accessToken = $options['access_token'];

        if (!empty($options['resource_owner_id'])) {
            $this->resourceOwnerId = $options['resource_owner_id'];
        }

        if (!empty($options['refresh_token'])) {
            $this->refreshToken = $options['refresh_token'];
        }

        // We need to know when the token expires. Show preference to
        // 'expires_in' since it is defined in RFC6749 Section 5.1.
        // Defer to 'expires' if it is provided instead.
        if (isset($options['expires_in'])) {
            if (!is_numeric($options['expires_in'])) {
                throw new \InvalidArgumentException('expires_in value must be an integer');
            }

            $this->expires = $options['expires_in'] != 0 ? time() + $options['expires_in'] : 0;
        } elseif (!empty($options['expires'])) {
            // Some providers supply the seconds until expiration rather than
            // the exact timestamp. Take a best guess at which we received.
            $expires = $options['expires'];

            if (!$this->isExpirationTimestamp($expires)) {
                $expires += time();
            }

            $this->expires = $expires;
        }

        // Capture any additional values that might exist in the token but are
        // not part of the standard response. Vendors will sometimes pass
        // additional user data this way.
        $this->values = array_diff_key($options, array_flip([
            'access_token',
            'resource_owner_id',
            'refresh_token',
            'expires_in',
            'expires',
        ]));
    }

    /**
     * Check if a value is an expiration timestamp or second value.
     *
     * @param integer $value
     * @return bool
     */
    protected function isExpirationTimestamp($value)
    {
        // If the given value is larger than the original OAuth 2 draft date,
        // assume that it is meant to be a (possible expired) timestamp.
        $oauth2InceptionDate = 1349067600; // 2012-10-01
        return ($value > $oauth2InceptionDate);
    }

    /**
     * @inheritdoc
     */
    public function getToken()
    {
        return $this->accessToken;
    }

    /**
     * @inheritdoc
     */
    public function getRefreshToken()
    {
        return $this->refreshToken;
    }

    /**
     * @inheritdoc
     */
    public function getExpires()
    {
        return $this->expires;
    }

    /**
     * @inheritdoc
     */
    public function getResourceOwnerId()
    {
        return $this->resourceOwnerId;
    }

    /**
     * @inheritdoc
     */
    public function hasExpired()
    {
        $expires = $this->getExpires();

        if (empty($expires)) {
            throw new RuntimeException('"expires" is not set on the token');
        }

        return $expires < time();
    }

    /**
     * @inheritdoc
     */
    public function getValues()
    {
        return $this->values;
    }

    /**
     * @inheritdoc
     */
    public function __toString()
    {
        return (string) $this->getToken();
    }

    /**
     * @inheritdoc
     */
    public function jsonSerialize()
    {
        $parameters = $this->values;

        if ($this->accessToken) {
            $parameters['access_token'] = $this->accessToken;
        }

        if ($this->refreshToken) {
            $parameters['refresh_token'] = $this->refreshToken;
        }

        if ($this->expires) {
            $parameters['expires'] = $this->expires;
        }

        if ($this->resourceOwnerId) {
            $parameters['resource_owner_id'] = $this->resourceOwnerId;
        }

        return $parameters;
    }
}
oauth2-client/src/Token/AccessTokenInterface.php000064400000003351147361032630015634 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Token;

use JsonSerializable;
use RuntimeException;

interface AccessTokenInterface extends JsonSerializable
{
    /**
     * Returns the access token string of this instance.
     *
     * @return string
     */
    public function getToken();

    /**
     * Returns the refresh token, if defined.
     *
     * @return string|null
     */
    public function getRefreshToken();

    /**
     * Returns the expiration timestamp, if defined.
     *
     * @return integer|null
     */
    public function getExpires();

    /**
     * Checks if this token has expired.
     *
     * @return boolean true if the token has expired, false otherwise.
     * @throws RuntimeException if 'expires' is not set on the token.
     */
    public function hasExpired();

    /**
     * Returns additional vendor values stored in the token.
     *
     * @return array
     */
    public function getValues();

    /**
     * Returns a string representation of the access token
     *
     * @return string
     */
    public function __toString();

    /**
     * Returns an array of parameters to serialize when this is serialized with
     * json_encode().
     *
     * @return array
     */
    public function jsonSerialize();
}
oauth2-client/src/Tool/GuardedPropertyTrait.php000064400000003414147361032630015572 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Tool;

/**
 * Provides support for blacklisting explicit properties from the
 * mass assignment behavior.
 */
trait GuardedPropertyTrait
{
    /**
     * The properties that aren't mass assignable.
     *
     * @var array
     */
    protected $guarded = [];

    /**
     * Attempts to mass assign the given options to explicitly defined properties,
     * skipping over any properties that are defined in the guarded array.
     *
     * @param array $options
     * @return mixed
     */
    protected function fillProperties(array $options = [])
    {
        if (isset($options['guarded'])) {
            unset($options['guarded']);
        }

        foreach ($options as $option => $value) {
            if (property_exists($this, $option) && !$this->isGuarded($option)) {
                $this->{$option} = $value;
            }
        }
    }

    /**
     * Returns current guarded properties.
     *
     * @return array
     */
    public function getGuarded()
    {
        return $this->guarded;
    }

    /**
     * Determines if the given property is guarded.
     *
     * @param  string  $property
     * @return bool
     */
    public function isGuarded($property)
    {
        return in_array($property, $this->getGuarded());
    }
}
oauth2-client/src/Tool/ProviderRedirectTrait.php000064400000006146147361032630015733 0ustar00<?php

namespace League\OAuth2\Client\Tool;

use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Uri;
use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

trait ProviderRedirectTrait
{
    /**
     * Maximum number of times to follow provider initiated redirects
     *
     * @var integer
     */
    protected $redirectLimit = 2;

    /**
     * Retrieves a response for a given request and retrieves subsequent
     * responses, with authorization headers, if a redirect is detected.
     *
     * @param  RequestInterface $request
     * @return ResponseInterface
     * @throws BadResponseException
     */
    protected function followRequestRedirects(RequestInterface $request)
    {
        $response = null;
        $attempts = 0;

        while ($attempts < $this->redirectLimit) {
            $attempts++;
            $response = $this->getHttpClient()->send($request, [
                'allow_redirects' => false
            ]);

            if ($this->isRedirect($response)) {
                $redirectUrl = new Uri($response->getHeader('Location')[0]);
                $request = $request->withUri($redirectUrl);
            } else {
                break;
            }
        }

        return $response;
    }

    /**
     * Returns the HTTP client instance.
     *
     * @return GuzzleHttp\ClientInterface
     */
    abstract public function getHttpClient();

    /**
     * Retrieves current redirect limit.
     *
     * @return integer
     */
    public function getRedirectLimit()
    {
        return $this->redirectLimit;
    }

    /**
     * Determines if a given response is a redirect.
     *
     * @param  ResponseInterface  $response
     *
     * @return boolean
     */
    protected function isRedirect(ResponseInterface $response)
    {
        $statusCode = $response->getStatusCode();

        return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location');
    }

    /**
     * Sends a request instance and returns a response instance.
     *
     * WARNING: This method does not attempt to catch exceptions caused by HTTP
     * errors! It is recommended to wrap this method in a try/catch block.
     *
     * @param  RequestInterface $request
     * @return ResponseInterface
     */
    public function getResponse(RequestInterface $request)
    {
        try {
            $response = $this->followRequestRedirects($request);
        } catch (BadResponseException $e) {
            $response = $e->getResponse();
        }

        return $response;
    }

    /**
     * Updates the redirect limit.
     *
     * @param integer $limit
     * @return League\OAuth2\Client\Provider\AbstractProvider
     * @throws InvalidArgumentException
     */
    public function setRedirectLimit($limit)
    {
        if (!is_int($limit)) {
            throw new InvalidArgumentException('redirectLimit must be an integer.');
        }

        if ($limit < 1) {
            throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.');
        }

        $this->redirectLimit = $limit;

        return $this;
    }
}
oauth2-client/src/Tool/RequestFactory.php000064400000004427147361032630014433 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Tool;

use GuzzleHttp\Psr7\Request;

/**
 * Used to produce PSR-7 Request instances.
 *
 * @link https://github.com/guzzle/guzzle/pull/1101
 */
class RequestFactory
{
    /**
     * Creates a PSR-7 Request instance.
     *
     * @param  null|string $method HTTP method for the request.
     * @param  null|string $uri URI for the request.
     * @param  array $headers Headers for the message.
     * @param  string|resource|StreamInterface $body Message body.
     * @param  string $version HTTP protocol version.
     *
     * @return Request
     */
    public function getRequest(
        $method,
        $uri,
        array $headers = [],
        $body = null,
        $version = '1.1'
    ) {
        return new Request($method, $uri, $headers, $body, $version);
    }

    /**
     * Parses simplified options.
     *
     * @param array $options Simplified options.
     *
     * @return array Extended options for use with getRequest.
     */
    protected function parseOptions(array $options)
    {
        // Should match default values for getRequest
        $defaults = [
            'headers' => [],
            'body'    => null,
            'version' => '1.1',
        ];

        return array_merge($defaults, $options);
    }

    /**
     * Creates a request using a simplified array of options.
     *
     * @param  null|string $method
     * @param  null|string $uri
     * @param  array $options
     *
     * @return Request
     */
    public function getRequestWithOptions($method, $uri, array $options = [])
    {
        $options = $this->parseOptions($options);

        return $this->getRequest(
            $method,
            $uri,
            $options['headers'],
            $options['body'],
            $options['version']
        );
    }
}
oauth2-client/src/Tool/ArrayAccessorTrait.php000064400000002637147361032630015221 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Tool;

/**
 * Provides generic array navigation tools.
 */
trait ArrayAccessorTrait
{
    /**
     * Returns a value by key using dot notation.
     *
     * @param  array      $data
     * @param  string     $key
     * @param  mixed|null $default
     * @return mixed
     */
    private function getValueByKey(array $data, $key, $default = null)
    {
        if (!is_string($key) || empty($key) || !count($data)) {
            return $default;
        }

        if (strpos($key, '.') !== false) {
            $keys = explode('.', $key);

            foreach ($keys as $innerKey) {
                if (!is_array($data) || !array_key_exists($innerKey, $data)) {
                    return $default;
                }

                $data = $data[$innerKey];
            }

            return $data;
        }

        return array_key_exists($key, $data) ? $data[$key] : $default;
    }
}
oauth2-client/src/Tool/MacAuthorizationTrait.php000064400000005012147361032630015727 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Tool;

use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Token\AccessTokenInterface;

/**
 * Enables `MAC` header authorization for providers.
 *
 * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens
 */
trait MacAuthorizationTrait
{
    /**
     * Returns the id of this token for MAC generation.
     *
     * @param  AccessToken $token
     * @return string
     */
    abstract protected function getTokenId(AccessToken $token);

    /**
     * Returns the MAC signature for the current request.
     *
     * @param  string $id
     * @param  integer $ts
     * @param  string $nonce
     * @return string
     */
    abstract protected function getMacSignature($id, $ts, $nonce);

    /**
     * Returns a new random string to use as the state parameter in an
     * authorization flow.
     *
     * @param  int $length Length of the random string to be generated.
     * @return string
     */
    abstract protected function getRandomState($length = 32);

    /**
     * Returns the authorization headers for the 'mac' grant.
     *
     * @param  AccessTokenInterface|string|null $token Either a string or an access token instance
     * @return array
     * @codeCoverageIgnore
     *
     * @todo This is currently untested and provided only as an example. If you
     * complete the implementation, please create a pull request for
     * https://github.com/thephpleague/oauth2-client
     */
    protected function getAuthorizationHeaders($token = null)
    {
        if ($token === null) {
            return [];
        }

        $ts    = time();
        $id    = $this->getTokenId($token);
        $nonce = $this->getRandomState(16);
        $mac   = $this->getMacSignature($id, $ts, $nonce);

        $parts = [];
        foreach (compact('id', 'ts', 'nonce', 'mac') as $key => $value) {
            $parts[] = sprintf('%s="%s"', $key, $value);
        }

        return ['Authorization' => 'MAC ' . implode(', ', $parts)];
    }
}
oauth2-client/src/Tool/BearerAuthorizationTrait.php000064400000002134147361032630016431 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Tool;

use League\OAuth2\Client\Token\AccessTokenInterface;

/**
 * Enables `Bearer` header authorization for providers.
 *
 * @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750)
 */
trait BearerAuthorizationTrait
{
    /**
     * Returns authorization headers for the 'bearer' grant.
     *
     * @param  AccessTokenInterface|string|null $token Either a string or an access token instance
     * @return array
     */
    protected function getAuthorizationHeaders($token = null)
    {
        return ['Authorization' => 'Bearer ' . $token];
    }
}
oauth2-client/src/Tool/QueryBuilderTrait.php000064400000001611147361032630015063 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Tool;

/**
 * Provides a standard way to generate query strings.
 */
trait QueryBuilderTrait
{
    /**
     * Build a query string from an array.
     *
     * @param array $params
     *
     * @return string
     */
    protected function buildQueryString(array $params)
    {
        return http_build_query($params, null, '&', \PHP_QUERY_RFC3986);
    }
}
oauth2-client/src/Tool/RequiredParameterTrait.php000064400000002776147361032630016105 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Tool;

use BadMethodCallException;

/**
 * Provides functionality to check for required parameters.
 */
trait RequiredParameterTrait
{
    /**
     * Checks for a required parameter in a hash.
     *
     * @throws BadMethodCallException
     * @param  string $name
     * @param  array  $params
     * @return void
     */
    private function checkRequiredParameter($name, array $params)
    {
        if (!isset($params[$name])) {
            throw new BadMethodCallException(sprintf(
                'Required parameter not passed: "%s"',
                $name
            ));
        }
    }

    /**
     * Checks for multiple required parameters in a hash.
     *
     * @throws InvalidArgumentException
     * @param  array $names
     * @param  array $params
     * @return void
     */
    private function checkRequiredParameters(array $names, array $params)
    {
        foreach ($names as $name) {
            $this->checkRequiredParameter($name, $params);
        }
    }
}
oauth2-client/src/Grant/ClientCredentials.php000064400000001737147361032630015206 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Grant;

/**
 * Represents a client credentials grant.
 *
 * @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, §1.3.4)
 */
class ClientCredentials extends AbstractGrant
{
    /**
     * @inheritdoc
     */
    protected function getName()
    {
        return 'client_credentials';
    }

    /**
     * @inheritdoc
     */
    protected function getRequiredRequestParameters()
    {
        return [];
    }
}
oauth2-client/src/Grant/Password.php000064400000002047147361032630013407 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Grant;

/**
 * Represents a resource owner password credentials grant.
 *
 * @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, §1.3.3)
 */
class Password extends AbstractGrant
{
    /**
     * @inheritdoc
     */
    protected function getName()
    {
        return 'password';
    }

    /**
     * @inheritdoc
     */
    protected function getRequiredRequestParameters()
    {
        return [
            'username',
            'password',
        ];
    }
}
oauth2-client/src/Grant/AuthorizationCode.php000064400000001775147361032630015247 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Grant;

/**
 * Represents an authorization code grant.
 *
 * @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, §1.3.1)
 */
class AuthorizationCode extends AbstractGrant
{
    /**
     * @inheritdoc
     */
    protected function getName()
    {
        return 'authorization_code';
    }

    /**
     * @inheritdoc
     */
    protected function getRequiredRequestParameters()
    {
        return [
            'code',
        ];
    }
}
oauth2-client/src/Grant/GrantFactory.php000064400000005144147361032630014211 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Grant;

use League\OAuth2\Client\Grant\Exception\InvalidGrantException;

/**
 * Represents a factory used when retrieving an authorization grant type.
 */
class GrantFactory
{
    /**
     * @var array
     */
    protected $registry = [];

    /**
     * Defines a grant singleton in the registry.
     *
     * @param  string $name
     * @param  AbstractGrant $grant
     * @return self
     */
    public function setGrant($name, AbstractGrant $grant)
    {
        $this->registry[$name] = $grant;

        return $this;
    }

    /**
     * Returns a grant singleton by name.
     *
     * If the grant has not be registered, a default grant will be loaded.
     *
     * @param  string $name
     * @return AbstractGrant
     */
    public function getGrant($name)
    {
        if (empty($this->registry[$name])) {
            $this->registerDefaultGrant($name);
        }

        return $this->registry[$name];
    }

    /**
     * Registers a default grant singleton by name.
     *
     * @param  string $name
     * @return self
     */
    protected function registerDefaultGrant($name)
    {
        // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode'
        $class = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name)));
        $class = 'League\\OAuth2\\Client\\Grant\\' . $class;

        $this->checkGrant($class);

        return $this->setGrant($name, new $class);
    }

    /**
     * Determines if a variable is a valid grant.
     *
     * @param  mixed $class
     * @return boolean
     */
    public function isGrant($class)
    {
        return is_subclass_of($class, AbstractGrant::class);
    }

    /**
     * Checks if a variable is a valid grant.
     *
     * @throws InvalidGrantException
     * @param  mixed $class
     * @return void
     */
    public function checkGrant($class)
    {
        if (!$this->isGrant($class)) {
            throw new InvalidGrantException(sprintf(
                'Grant "%s" must extend AbstractGrant',
                is_object($class) ? get_class($class) : $class
            ));
        }
    }
}
oauth2-client/src/Grant/Exception/InvalidGrantException.php000064400000001424147361032630020002 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Grant\Exception;

use InvalidArgumentException;

/**
 * Exception thrown if the grant does not extend from AbstractGrant.
 *
 * @see League\OAuth2\Client\Grant\AbstractGrant
 */
class InvalidGrantException extends InvalidArgumentException
{
}
oauth2-client/src/Grant/RefreshToken.php000064400000001766147361032630014213 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Grant;

/**
 * Represents a refresh token grant.
 *
 * @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, §6)
 */
class RefreshToken extends AbstractGrant
{
    /**
     * @inheritdoc
     */
    protected function getName()
    {
        return 'refresh_token';
    }

    /**
     * @inheritdoc
     */
    protected function getRequiredRequestParameters()
    {
        return [
            'refresh_token',
        ];
    }
}
oauth2-client/src/Grant/AbstractGrant.php000064400000004645147361032630014352 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\Grant;

use League\OAuth2\Client\Tool\RequiredParameterTrait;

/**
 * Represents a type of authorization grant.
 *
 * An authorization grant is a credential representing the resource
 * owner's authorization (to access its protected resources) used by the
 * client to obtain an access token.  OAuth 2.0 defines four
 * grant types -- authorization code, implicit, resource owner password
 * credentials, and client credentials -- as well as an extensibility
 * mechanism for defining additional types.
 *
 * @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, §1.3)
 */
abstract class AbstractGrant
{
    use RequiredParameterTrait;

    /**
     * Returns the name of this grant, eg. 'grant_name', which is used as the
     * grant type when encoding URL query parameters.
     *
     * @return string
     */
    abstract protected function getName();

    /**
     * Returns a list of all required request parameters.
     *
     * @return array
     */
    abstract protected function getRequiredRequestParameters();

    /**
     * Returns this grant's name as its string representation. This allows for
     * string interpolation when building URL query parameters.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->getName();
    }

    /**
     * Prepares an access token request's parameters by checking that all
     * required parameters are set, then merging with any given defaults.
     *
     * @param  array $defaults
     * @param  array $options
     * @return array
     */
    public function prepareRequestParameters(array $defaults, array $options)
    {
        $defaults['grant_type'] = $this->getName();

        $required = $this->getRequiredRequestParameters();
        $provided = array_merge($defaults, $options);

        $this->checkRequiredParameters($required, $provided);

        return $provided;
    }
}
oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php000064400000002646147361032630021131 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\OptionProvider;

use InvalidArgumentException;

/**
 * Add http basic auth into access token request options
 * @link https://tools.ietf.org/html/rfc6749#section-2.3.1
 */
class HttpBasicAuthOptionProvider extends PostAuthOptionProvider
{
    /**
     * @inheritdoc
     */
    public function getAccessTokenOptions($method, array $params)
    {
        if (empty($params['client_id']) || empty($params['client_secret'])) {
            throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth');
        }

        $encodedCredentials = base64_encode(sprintf('%s:%s', $params['client_id'], $params['client_secret']));
        unset($params['client_id'], $params['client_secret']);

        $options = parent::getAccessTokenOptions($method, $params);
        $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials;

        return $options;
    }
}
oauth2-client/src/OptionProvider/OptionProviderInterface.php000064400000001573147361032630020324 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\OptionProvider;

/**
 * Interface for access token options provider
 */
interface OptionProviderInterface
{
    /**
     * Builds request options used for requesting an access token.
     *
     * @param string $method
     * @param  array $params
     * @return array
     */
    public function getAccessTokenOptions($method, array $params);
}
oauth2-client/src/OptionProvider/PostAuthOptionProvider.php000064400000002622147361032630020167 0ustar00<?php
/**
 * This file is part of the league/oauth2-client library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
 * @license http://opensource.org/licenses/MIT MIT
 * @link http://thephpleague.com/oauth2-client/ Documentation
 * @link https://packagist.org/packages/league/oauth2-client Packagist
 * @link https://github.com/thephpleague/oauth2-client GitHub
 */

namespace League\OAuth2\Client\OptionProvider;

use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Tool\QueryBuilderTrait;

/**
 * Provide options for access token
 */
class PostAuthOptionProvider implements OptionProviderInterface
{
    use QueryBuilderTrait;

    /**
     * @inheritdoc
     */
    public function getAccessTokenOptions($method, array $params)
    {
        $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']];

        if ($method === AbstractProvider::METHOD_POST) {
            $options['body'] = $this->getAccessTokenBody($params);
        }

        return $options;
    }

    /**
     * Returns the request body for requesting an access token.
     *
     * @param  array $params
     * @return string
     */
    protected function getAccessTokenBody(array $params)
    {
        return $this->buildQueryString($params);
    }
}
oauth2-client/README.md000064400000034677147361032630010527 0ustar00# OAuth 2.0 Client

This package makes it simple to integrate your application with [OAuth 2.0](http://oauth.net/2/) service providers.

[![Gitter Chat](https://img.shields.io/badge/gitter-join_chat-brightgreen.svg?style=flat-square)](https://gitter.im/thephpleague/oauth2-client)
[![Source Code](http://img.shields.io/badge/source-thephpleague/oauth2--client-blue.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client)
[![Latest Version](https://img.shields.io/github/release/thephpleague/oauth2-client.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE)
[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-client/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/oauth2-client)
[![Scrutinizer](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-client/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-client/)
[![Coverage Status](https://img.shields.io/coveralls/thephpleague/oauth2-client/master.svg?style=flat-square)](https://coveralls.io/r/thephpleague/oauth2-client?branch=master)
[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-client.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-client)

---

We are all used to seeing those "Connect with Facebook/Google/etc." buttons around the internet, and social network integration is an important feature of most web applications these days. Many of these sites use an authentication and authorization standard called OAuth 2.0 ([RFC 6749](http://tools.ietf.org/html/rfc6749)).

This OAuth 2.0 client library will work with any OAuth provider that conforms to the OAuth 2.0 standard. Out-of-the-box, we provide a `GenericProvider` that may be used to connect to any service provider that uses [Bearer tokens](http://tools.ietf.org/html/rfc6750) (see example below).

Many service providers provide additional functionality above and beyond the OAuth 2.0 standard. For this reason, this library may be easily extended and wrapped to support this additional behavior. We provide links to [all known provider clients extending this library](docs/providers/thirdparty.md) (i.e. Facebook, GitHub, Google, Instagram, LinkedIn, etc.). If your provider isn't in the list, feel free to add it.

This package is compliant with [PSR-1][], [PSR-2][], [PSR-4][], and [PSR-7][]. If you notice compliance oversights, please send a patch via pull request. If you're interesting in contributing to this library, please take a look at our [contributing guidelines](CONTRIBUTING.md).

## Requirements

The following versions of PHP are supported.

* PHP 5.6
* PHP 7.0
* PHP 7.1
* PHP 7.2
* PHP 7.3

## Providers

A list of official PHP League providers, as well as third-party providers, may be found in the [providers list README](docs/providers/thirdparty.md).

To build your own provider, please refer to the [provider guide README](README.PROVIDER-GUIDE.md).

## Usage

**In most cases, you'll want to use a specific provider client library rather than this base library.**

Take a look at [providers list README](docs/providers/thirdparty.md) to see a list of provider client libraries.

If using Composer to require a specific provider client library, you **do not need to also require this library**. Composer will handle the dependencies for you.

### Authorization Code Grant

The following example uses the out-of-the-box `GenericProvider` provided by this library. If you're looking for a specific provider (i.e. Facebook, Google, GitHub, etc.), take a look at our [list of provider client libraries](docs/providers/thirdparty.md). **HINT: You're probably looking for a specific provider.**

The authorization code grant type is the most common grant type used when authenticating users with a third-party service. This grant type utilizes a client (this library), a server (the service provider), and a resource owner (the user with credentials to a protected—or owned—resource) to request access to resources owned by the user. This is often referred to as _3-legged OAuth_, since there are three parties involved.

The following example illustrates this using [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. When running this code, you will be redirected to Lock'd In, where you'll be prompted to authorize the client to make requests to a resource on your behalf.

Now, you don't really have an account on Lock'd In, but for the sake of this example, imagine that you are already logged in on Lock'd In when you are redirected there.

```php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'demoapp',    // The client ID assigned to you by the provider
    'clientSecret'            => 'demopass',   // The client password assigned to you by the provider
    'redirectUri'             => 'http://example.com/your-redirect-url/',
    'urlAuthorize'            => 'http://brentertainment.com/oauth2/lockdin/authorize',
    'urlAccessToken'          => 'http://brentertainment.com/oauth2/lockdin/token',
    'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource'
]);

// If we don't have an authorization code then get one
if (!isset($_GET['code'])) {

    // Fetch the authorization URL from the provider; this returns the
    // urlAuthorize option and generates and applies any necessary parameters
    // (e.g. state).
    $authorizationUrl = $provider->getAuthorizationUrl();

    // Get the state generated for you and store it to the session.
    $_SESSION['oauth2state'] = $provider->getState();

    // Redirect the user to the authorization URL.
    header('Location: ' . $authorizationUrl);
    exit;

// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) {

    if (isset($_SESSION['oauth2state'])) {
        unset($_SESSION['oauth2state']);
    }
    
    exit('Invalid state');

} else {

    try {

        // Try to get an access token using the authorization code grant.
        $accessToken = $provider->getAccessToken('authorization_code', [
            'code' => $_GET['code']
        ]);

        // We have an access token, which we may use in authenticated
        // requests against the service provider's API.
        echo 'Access Token: ' . $accessToken->getToken() . "<br>";
        echo 'Refresh Token: ' . $accessToken->getRefreshToken() . "<br>";
        echo 'Expired in: ' . $accessToken->getExpires() . "<br>";
        echo 'Already expired? ' . ($accessToken->hasExpired() ? 'expired' : 'not expired') . "<br>";

        // Using the access token, we may look up details about the
        // resource owner.
        $resourceOwner = $provider->getResourceOwner($accessToken);

        var_export($resourceOwner->toArray());

        // The provider provides a way to get an authenticated API request for
        // the service, using the access token; it returns an object conforming
        // to Psr\Http\Message\RequestInterface.
        $request = $provider->getAuthenticatedRequest(
            'GET',
            'http://brentertainment.com/oauth2/lockdin/resource',
            $accessToken
        );

    } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {

        // Failed to get the access token or user details.
        exit($e->getMessage());

    }

}
```

### Refreshing a Token

Once your application is authorized, you can refresh an expired token using a refresh token rather than going through the entire process of obtaining a brand new token. To do so, simply reuse this refresh token from your data store to request a refresh.

_This example uses [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. See authorization code example above, for more details._

```php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'demoapp',    // The client ID assigned to you by the provider
    'clientSecret'            => 'demopass',   // The client password assigned to you by the provider
    'redirectUri'             => 'http://example.com/your-redirect-url/',
    'urlAuthorize'            => 'http://brentertainment.com/oauth2/lockdin/authorize',
    'urlAccessToken'          => 'http://brentertainment.com/oauth2/lockdin/token',
    'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource'
]);

$existingAccessToken = getAccessTokenFromYourDataStore();

if ($existingAccessToken->hasExpired()) {
    $newAccessToken = $provider->getAccessToken('refresh_token', [
        'refresh_token' => $existingAccessToken->getRefreshToken()
    ]);

    // Purge old access token and store new access token to your data store.
}
```

### Resource Owner Password Credentials Grant

Some service providers allow you to skip the authorization code step to exchange a user's credentials (username and password) for an access token. This is referred to as the "resource owner password credentials" grant type.

According to [section 1.3.3](http://tools.ietf.org/html/rfc6749#section-1.3.3) of the OAuth 2.0 standard (emphasis added):

> The credentials **should only be used when there is a high degree of trust**
> between the resource owner and the client (e.g., the client is part of the
> device operating system or a highly privileged application), and when other
> authorization grant types are not available (such as an authorization code).

**We do not advise using this grant type if the service provider supports the authorization code grant type (see above), as this reinforces the [password anti-pattern](https://agentile.com/the-password-anti-pattern) by allowing users to think it's okay to trust third-party applications with their usernames and passwords.**

That said, there are use-cases where the resource owner password credentials grant is acceptable and useful. Here's an example using it with [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. See authorization code example above, for more details about the Lock'd In demo application.

``` php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'demoapp',    // The client ID assigned to you by the provider
    'clientSecret'            => 'demopass',   // The client password assigned to you by the provider
    'redirectUri'             => 'http://example.com/your-redirect-url/',
    'urlAuthorize'            => 'http://brentertainment.com/oauth2/lockdin/authorize',
    'urlAccessToken'          => 'http://brentertainment.com/oauth2/lockdin/token',
    'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource'
]);

try {

    // Try to get an access token using the resource owner password credentials grant.
    $accessToken = $provider->getAccessToken('password', [
        'username' => 'demouser',
        'password' => 'testpass'
    ]);

} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {

    // Failed to get the access token
    exit($e->getMessage());

}
```

### Client Credentials Grant

When your application is acting on its own behalf to access resources it controls/owns in a service provider, it may use the client credentials grant type. This is best used when the credentials for your application are stored privately and never exposed (e.g. through the web browser, etc.) to end-users. This grant type functions similarly to the resource owner password credentials grant type, but it does not request a user's username or password. It uses only the client ID and secret issued to your client by the service provider.

Unlike earlier examples, the following does not work against a functioning demo service provider. It is provided for the sake of example only.

``` php
// Note: the GenericProvider requires the `urlAuthorize` option, even though
// it's not used in the OAuth 2.0 client credentials grant type.

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'XXXXXX',    // The client ID assigned to you by the provider
    'clientSecret'            => 'XXXXXX',    // The client password assigned to you by the provider
    'redirectUri'             => 'http://my.example.com/your-redirect-url/',
    'urlAuthorize'            => 'http://service.example.com/authorize',
    'urlAccessToken'          => 'http://service.example.com/token',
    'urlResourceOwnerDetails' => 'http://service.example.com/resource'
]);

try {

    // Try to get an access token using the client credentials grant.
    $accessToken = $provider->getAccessToken('client_credentials');

} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {

    // Failed to get the access token
    exit($e->getMessage());

}
```

### Using a proxy

It is possible to use a proxy to debug HTTP calls made to a provider. All you need to do is set the `proxy` and `verify` options when creating your Provider instance. Make sure you enable SSL proxying in your proxy.

``` php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'XXXXXX',    // The client ID assigned to you by the provider
    'clientSecret'            => 'XXXXXX',    // The client password assigned to you by the provider
    'redirectUri'             => 'http://my.example.com/your-redirect-url/',
    'urlAuthorize'            => 'http://service.example.com/authorize',
    'urlAccessToken'          => 'http://service.example.com/token',
    'urlResourceOwnerDetails' => 'http://service.example.com/resource',
    'proxy'                   => '192.168.0.1:8888',
    'verify'                  => false
]);
```

## Install

Via Composer

``` bash
$ composer require league/oauth2-client
```

## Contributing

Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details.

## License

The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information.


[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
[PSR-7]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md
oauth2-client/README.PROVIDER-GUIDE.md000064400000010215147361032630012571 0ustar00# OAuth 2.0 Client

## Provider Guide

New providers may be created by copying the layout of an existing package. See
the [list of providers](docs/providers/thirdparty.md) for good examples.

When choosing a name for your package, please don’t use the `league` vendor
prefix, as this implies that it is officially supported. You should use your own
username as the vendor prefix, and prepend `oauth2-` to the package name to make
it clear that your package works with OAuth2 Client. For example, if your GitHub
username was "santa," and you were implementing the "giftpay" OAuth2 library, a
good name for your composer package would be `santa/oauth2-giftpay`.

### Implementing your own provider

If you are working with an oauth2 service not supported out-of-the-box or by an
existing package, it is quite simple to implement your own. Simply extend
[`League\OAuth2\Client\Provider\AbstractProvider`](src/Provider/AbstractProvider.php)
and implement the required abstract methods:

```php
abstract public function getBaseAuthorizationUrl();
abstract public function getBaseAccessTokenUrl(array $params);
abstract public function getResourceOwnerDetailsUrl(AccessToken $token);
abstract protected function getDefaultScopes();
abstract protected function checkResponse(ResponseInterface $response, $data);
abstract protected function createResourceOwner(array $response, AccessToken $token);
```

Each of these abstract methods contain a docblock defining their expectations
and typical behavior. Once you have extended this class, you can simply follow
the [usage example in the README](README.md#usage) using your new `Provider`.

If you wish to use the `Provider` to make authenticated requests to the
service, you will also need to define how you provide the token to the
service. If this is done via headers, you should override this method:

```php
protected function getAuthorizationHeaders($token = null);
```

This package comes with a trait for implementing `Bearer` authorization.
To use this, you just need to include the trait in your `Provider` class:

```php
<?php
class SomeProvider extends AbstractProvider
{
    use League\OAuth2\Client\Tool\BearerAuthorizationTrait;

    /** ... **/
}
```


### Resource owner identifiers in access token responses

In services where the resource owner is a person, the resource owner is sometimes
referred to as an end-user.

We have decided to abstract away as much of the resource owner details as possible,
since these are not part of the OAuth 2.0 specification and are very specific to each
service provider. This provides greater flexibility to each provider, allowing
them to handle the implementation details for resource owners.

The `AbstractProvider` does not specify an access token resource owner identifier. It is
the responsibility of the provider class to set the `ACCESS_TOKEN_RESOURCE_OWNER_ID` constant
to the string value of the key used in the access token response to identify the
resource owner.

```php
/**
 * @var string Key used in the access token response to identify the resource owner.
 */
const ACCESS_TOKEN_RESOURCE_OWNER_ID = null;
```

Once this is set on your provider, when calling `AbstractProvider::getAccessToken()`,
the `AccessToken` returned will have its `$resourceOwnerId` property set, which you may
retrieve by calling `AccessToken::getResourceOwnerId()`.

The next step is to implement the `AbstractProvider::createResourceOwner()` method. This
method accepts as parameters a response array and an `AccessToken`. You may use
this information in order to request resource owner details from your service and
construct and return an object that implements
[`League\OAuth2\Client\Provider\ResourceOwnerInterface`](src/Provider/ResourceOwnerInterface.php).
This object is returned when calling `AbstractProvider::getResourceOwner()`.

### Make your gateway official

If you want to transfer your provider to the `thephpleague` GitHub organization
and add it to the list of officially supported providers, please open a pull
request on the thephpleague/oauth2-client package. Before new providers will be
accepted, they must have 100% unit test code coverage, and follow the
conventions and code style used in other OAuth2 Client providers.
oauth2-client/CHANGELOG.md000064400000026247147361032630011053 0ustar00# OAuth 2.0 Client Changelog

## 2.5.0

_Released: 2020-07-18_

* Allow Guzzle 7.x to be used [#847](https://github.com/thephpleague/oauth2-client/pull/847)

## 2.4.1

_Released: 2018-11-22_

* Revert to use of `AccessToken` in type hints to preserve backwards
  compatibility; this fixes the issue reported in [#752](https://github.com/thephpleague/oauth2-client/issues/752)
  and [#753](https://github.com/thephpleague/oauth2-client/issues/753)

## 2.4.0

_Released: 2018-11-21_

* Add `HttpBasicAuthOptionProvider` to ease implementation for providers
  requiring HTTP basic auth
* Add `GuardedPropertyTrait` to allow providers the ability to specify
  properties that may not be overridden by user-defined values passed to the
  provider constructor
* Add `AccessTokenInterface` and `ResourceOwnerAccessTokenInterface` to allow
  providers the ability to override the default `AccessToken`

## 2.3.1

_Released: 2018-11-19_

* Allow paragonie/random_compat's empty 9.99.99 placeholder
* Throw an `UnexpectedValueException` on non-JSON responses from access token
  request (when calling `AbstractProvider::getAccessToken()`)

## 2.3.0

_Released: 2018-01-13_

* Add `ProviderRedirectTrait` tool for 3rd-party provider libraries to use when
  handling provider redirections
* Fix TypeError thrown because `getResourceOwner()` receives a non-JSON Response
* Gracefully handle non-standard errors received from providers
* Update README to reflect official support of PHP 7.2

## 2.2.1

_Released: 2017-04-25_

* Fix potential type error when HTTP 500 errors are encountered
* Allow broader range of `random_compat` versions

## 2.2.0

_Released: 2017-02-01_

* Allow base URLs to contain query parameters
* Protect against `+` being improperly encoded in URL parameters
* Remove misleading `state` option from authorization parameters
* Stop generating more random bytes than necessary

## 2.1.0

_Released: 2017-01-24_

* Allow `expires_in` with a value of `0`

## 2.0.0

_Released: 2017-01-12_

* Rename `getResponse()` to `getParsedResponse()`
* Add `getResponse()` method that returns the unparsed PSR-7 `Response` instance
* Removed `RandomFactory`, switched to native random functions

## 1.4.1

_Released: 2016-04-29_

* Add `QueryBuilderTrait` to standardize query string generation.

## 1.4.0

_Released: 2016-04-19_

* Add `AccessToken::getValues()` to access additional vendor data provided with tokens.

## 1.3.0

_Released: 2016-02-13_

* Enable dynamic parameters being passed into the authorization URL.
* Minor documentation updates.

## 1.2.0

_Released: 2016-01-23_

* Add `resource_owner_id` to the JSON-serialized representation of the access token.
* Minor documentation updates and improved test coverage.

## 1.1.0

_Released: 2015-11-13_

* Add `ArrayAccessorTrait`, update `AbstractProvider` to utilize.
* Use `expires` to serialize access tokens.
* Documentation updates.

## 1.0.2

_Released: 2015-09-22_

* Allow access tokens to be created from storage (see #431).
* Minor fixes and documentation updates.

## 1.0.1

_Released: 2015-08-26_

* Allow required parameters checked using the `RequiredParameterTrait` to be set as `false`, `null`, `"0"`, etc.

## 1.0.0

_Released: 2015-08-19_

* We are running code-quality builds through Scrutinizer, and we are running unit test builds on the new Travis CI container-based infrastructure.
* Cleaned up code, as recommended by Scrutinizer.
* Documentation updates.

## 1.0.0-beta2

_Released: 2015-08-12_

* BREAK: Add toArray() to ResourceOwnerInterface.
* Always attempt to parse responses as JSON and fallback on failure.
* Add dot notation support to access token resource owner ID.
* Use the Bearer authorization header for the generic provider.
* Documentation updates.

## 1.0.0-beta1

_Released: 2015-07-16_

* API for 1.0 is now frozen!
* BREAK: Convert all uses of "User" to "ResourceOwner" to more closely match the OAuth 2.0 specification.
* BREAK: Rename `StandardProvider` to `GenericProvider`.
* BREAK: Move access token creation to the `AbstractProvider`. It was previously handled in the `AbstractGrant`.
* FIX: Add `Content-Type` header with value of `application/x-www-form-urlencoded` to the request header when retrieving access tokens. This adheres to the OAuth 2.0 specification and fixes issues where certain OAuth servers expect this header.
* Enhanced `json_encode()` serialization of AccessToken; when using `json_encode()` on an AccessToken, it will return a JSON object with these properties: `access_token`, `refresh_token`, and `expires_in`.

## 1.0.0-alpha2

_Released: 2015-07-04_

* BREAK: Renamed `AbstractProvider::ACCESS_TOKEN_METHOD_GET` to `AbstractProvider::METHOD_GET`.
* BREAK: Renamed `AbstractProvider::ACCESS_TOKEN_METHOD_POST` to `AbstractProvider::METHOD_POST`.
* BREAK: Renamed `AbstractProvider::prepareUserDetails()` to `AbstractProvider::createUser()`.
* BREAK: Renamed `AbstractProvider::getUserDetails()` to `AbstractProvider::getUser()`.
* BREAK: Removed `$token` parameter from `AbstractProvider::getDefaultHeaders()`.
* BREAK: Modify `AbstractProvider::getBaseAccessTokenUrl()` to accept a required array of parameters, allowing providers the ability to vary the access token URL, based on the parameters.
* Removed newline characters from MAC Authorization header.
* Documentation updates, notably:
  - Moved list of providers to `README.PROVIDERS.md`.
  - Moved provider creation notes to `README.PROVIDER-GUIDE.md`.

## 1.0.0-alpha1

_Released: 2015-06-25_

This release contains numerous BC breaks from the 0.x series. Please note these breaks and refer to the [upgrade guide](GUIDE-UPGRADING.md).

* BREAK: Requires PHP 5.5.0 and greater.
* BREAK: All providers have been moved to separate repositories, one for each provider.
* BREAK: All `public` properties have been set as `protected` or `private` and getters/setters have been introduced for access to these properties.
* BREAK: The `Provider\ProviderInterface` has been removed. Please extend from and override `Provider\AbstractProvider`.
* BREAK: The `Entity\User` has been removed. Providers should implement the `Provider\UserInterface` and provide user functionality instead of expecting it in this base library.
* BREAK: The `Grant\GrantInterface` has been removed. Providers needing to provide a new grant type should extend from and override `Grant\AbstractGrant`.
* A generic `Provider\StandardProvider` has been introduced, which may be used as a client to integrate with most OAuth 2.0 compatible servers.
* A `Grant\GrantFactory` has been introduced as a means to register and retrieve singleton grants from a registry.
* Introduced traits for bearer and MAC authorization (`Tool\BearerAuthorizationTrait` and `Tool\MacAuthorizationTrait`), which providers may use to enable these header authorization types.

## 0.12.1

_Released: 2015-06-20_

* FIX: Scope separators for LinkedIn and Instagram are now correctly a single space

## 0.12.0

_Released: 2015-06-15_

* BREAK: LinkedIn Provider: Default scopes removed from LinkedIn Provider. See "[Managing LinkedIn Scopes](https://github.com/thephpleague/oauth2-client/blob/9cea9864c2e89bce1b922d1e37ba5378b3b0b264/README.md#managing-linkedin-scopes)" in the README for information on how to set scopes. See [#327](https://github.com/thephpleague/oauth2-client/pull/327) and [#307](https://github.com/thephpleague/oauth2-client/pull/307) for details on this change.
* FIX: LinkedIn Provider: A scenario existed in which `publicProfileUrl` was not set, generating a PHP notice; this has been fixed.
* FIX: Instagram Provider: Fixed scope separator.
* Documentation updates and corrections.


## 0.11.0

_Released: 2015-04-25_

* Identity Provider: Better handling of error responses
* Documentation updates


## 0.10.1

_Released: 2015-04-02_

* FIX: Invalid JSON triggering fatal error
* FIX: Sending headers along with auth `getAccessToken()` requests
* Now running Travis CI tests on PHP 7
* Documentation updates


## 0.10.0

_Released: 2015-03-10_

* Providers: Added `getHeaders()` to ProviderInterface and updated AbstractProvider to provide the method
* Providers: Updated all bundled providers to support new `$authorizationHeader` property
* Identity Provider: Update IDPException to account for empty strings
* Identity Provider: Added `getResponseBody()` method to IDPException
* Documentation updates, minor bug fixes, and coding standards fixes


## 0.9.0

_Released: 2015-02-24_

* Add `AbstractProvider::prepareAccessTokenResult()` to provide additional token response preparation to providers
* Remove custom provider code from AccessToken
* Add links to README for Dropbox and Square providers


## 0.8.1

_Released: 2015-02-12_

* Allow `approval_prompt` to be set by providers. This fixes an issue where some providers have problems if the `approval_prompt` is present in the query string.


## 0.8.0

_Released: 2015-02-10_

* Facebook Provider: Upgrade to Graph API v2.2
* Google Provider: Add `access_type` parameter for Google authorization URL
* Get a more reliable response body on errors


## 0.7.2

_Released: 2015-02-03_

* GitHub Provider: Fix regression
* Documentation updates


## 0.7.1

_Released: 2015-01-06_

* Google Provider: fixed issue where Google API was not returning the user ID


## 0.7.0

_Released: 2014-12-29_

* Improvements to Provider\AbstractProvider (addition of `userUid()`, `userEmail()`, and `userScreenName()`)
* GitHub Provider: Support for GitHub Enterprise
* GitHub Provider: Methods to allow fetching user email addresses
* Google Provider: Updated scopes and endpoints to remove deprecated values
* Documentation updates, minor bug fixes, and coding standards fixes


## 0.6.0

_Released: 2014-12-03_

* Added ability to specify a redirect handler for providers through use of a callback (see [Provider\AbstractProvider::setRedirectHandler()](https://github.com/thephpleague/oauth2-client/blob/55de45401eaa21f53c0b2414091da6f3b0f3fcb7/src/Provider/AbstractProvider.php#L314-L317))
* Updated authorize and token URLs for the Microsoft provider; the old URLs had been phased out and were no longer working (see #146)
* Increased test coverage
* Documentation updates, minor bug fixes, and coding standards fixes


## 0.5.0

_Released: 2014-11-28_

* Added `ClientCredentials` and `Password` grants
* Added support for providers to set their own `uid` parameter key name
* Added support for Google's `hd` (hosted domain) parameter
* Added support for providing a custom `state` parameter to the authorization URL
* LinkedIn `pictureUrl` is now an optional response element
* Added Battle.net provider package link to README
* Added Meetup provider package link to README
* Added `.gitattributes` file
* Increased test coverage
* A number of documentation fixes, minor bug fixes, and coding standards fixes


## 0.4.0

_Released: 2014-10-28_

* Added  `ProviderInterface` and removed `IdentityProvider`.
* Expose generated state to allow for CSRF validation.
* Renamed `League\OAuth2\Client\Provider\User` to `League\OAuth2\Client\Entity\User`.
* Entity: User: added `gender` and `locale` properties
* Updating logic for populating the token expiration time.


## 0.3.0

_Released: 2014-04-26_

* This release made some huge leaps forward, including 100% unit-coverage and a bunch of new features.


## 0.2.0

_Released: 2013-05-28_

* No release notes available.


## 0.1.0

_Released: 2013-05-25_

* Initial release.
oauth2-client/LICENSE000064400000002122147361032630010231 0ustar00The MIT License (MIT)

Copyright (c) 2013-2018 Alex Bilbie <hello@alexbilbie.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
oauth2-google/CONTRIBUTING.md000064400000003251147361032630011457 0ustar00# Contributing

Contributions are **welcome** and will be fully **credited**.

We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-google).


## Pull Requests

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option.

- **Create topic branches** - Don't ask us to pull from your master branch.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.

- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass.

- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails.


## Running Tests

```sh
composer test
```


## Running PHP Code Sniffer

```sh
composer check
```

**Happy coding**!
oauth2-google/src/Provider/GoogleUser.php000064400000003623147361032630014376 0ustar00<?php

namespace League\OAuth2\Client\Provider;

class GoogleUser implements ResourceOwnerInterface
{
    /**
     * @var array
     */
    protected $response;

    /**
     * @param array $response
     */
    public function __construct(array $response)
    {
        $this->response = $response;
    }

    public function getId()
    {
        return $this->response['sub'];
    }

    /**
     * Get preferred display name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->response['name'];
    }

    /**
     * Get preferred first name.
     *
     * @return string|null
     */
    public function getFirstName()
    {
        return $this->getResponseValue('given_name');
    }

    /**
     * Get preferred last name.
     *
     * @return string|null
     */
    public function getLastName()
    {
        return $this->getResponseValue('family_name');
    }

    /**
     * Get locale.
     *
     * @return string|null
     */
    public function getLocale()
    {
        return $this->getResponseValue('locale');
    }

    /**
     * Get email address.
     *
     * @return string|null
     */
    public function getEmail()
    {
        return $this->getResponseValue('email');
    }

    /**
     * Get hosted domain.
     *
     * @return string|null
     */
    public function getHostedDomain()
    {
        return $this->getResponseValue('hd');
    }

    /**
     * Get avatar image URL.
     *
     * @return string|null
     */
    public function getAvatar()
    {
        return $this->getResponseValue('picture');
    }

    /**
     * Get user data as an array.
     *
     * @return array
     */
    public function toArray()
    {
        return $this->response;
    }

    private function getResponseValue($key)
    {
        if (array_key_exists($key, $this->response)) {
            return $this->response[$key];
        }
        return null;
    }
}
oauth2-google/src/Provider/Google.php000064400000010517147361032630013537 0ustar00<?php

namespace League\OAuth2\Client\Provider;

use League\OAuth2\Client\Exception\HostedDomainException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;

class Google extends AbstractProvider
{
    use BearerAuthorizationTrait;

    /**
     * @var string If set, this will be sent to google as the "access_type" parameter.
     * @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
     */
    protected $accessType;

    /**
     * @var string If set, this will be sent to google as the "hd" parameter.
     * @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
     */
    protected $hostedDomain;

    /**
     * @var string If set, this will be sent to google as the "prompt" parameter.
     * @link https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
     */
    protected $prompt;

    /**
     * @var array List of scopes that will be used for authentication.
     * @link https://developers.google.com/identity/protocols/googlescopes
     */
    protected $scopes = [];

    public function getBaseAuthorizationUrl()
    {
        return 'https://accounts.google.com/o/oauth2/v2/auth';
    }

    public function getBaseAccessTokenUrl(array $params)
    {
        return 'https://www.googleapis.com/oauth2/v4/token';
    }

    public function getResourceOwnerDetailsUrl(AccessToken $token)
    {
        return 'https://openidconnect.googleapis.com/v1/userinfo';
    }

    protected function getAuthorizationParameters(array $options)
    {
        if (empty($options['hd']) && $this->hostedDomain) {
            $options['hd'] = $this->hostedDomain;
        }

        if (empty($options['access_type']) && $this->accessType) {
            $options['access_type'] = $this->accessType;
        }

        if (empty($options['prompt']) && $this->prompt) {
            $options['prompt'] = $this->prompt;
        }

        // Default scopes MUST be included for OpenID Connect.
        // Additional scopes MAY be added by constructor or option.
        $scopes = array_merge($this->getDefaultScopes(), $this->scopes);

        if (!empty($options['scope'])) {
            $scopes = array_merge($scopes, $options['scope']);
        }

        $options['scope'] = array_unique($scopes);

        $options = parent::getAuthorizationParameters($options);

        // The "approval_prompt" MUST be removed as it is not supported by Google, use "prompt" instead:
        // https://developers.google.com/identity/protocols/oauth2/openid-connect#prompt
        unset($options['approval_prompt']);

        return $options;
    }

    protected function getDefaultScopes()
    {
        // "openid" MUST be the first scope in the list.
        return [
            'openid',
            'email',
            'profile',
        ];
    }

    protected function getScopeSeparator()
    {
        return ' ';
    }

    protected function checkResponse(ResponseInterface $response, $data)
    {
        // @codeCoverageIgnoreStart
        if (empty($data['error'])) {
            return;
        }
        // @codeCoverageIgnoreEnd

        $code = 0;
        $error = $data['error'];

        if (is_array($error)) {
            $code = $error['code'];
            $error = $error['message'];
        }

        throw new IdentityProviderException($error, $code, $data);
    }

    protected function createResourceOwner(array $response, AccessToken $token)
    {
        $user = new GoogleUser($response);

        $this->assertMatchingDomain($user->getHostedDomain());

        return $user;
    }

    /**
     * @throws HostedDomainException If the domain does not match the configured domain.
     */
    protected function assertMatchingDomain($hostedDomain)
    {
        if ($this->hostedDomain === null) {
            // No hosted domain configured.
            return;
        }

        if ($this->hostedDomain === '*' && $hostedDomain) {
            // Any hosted domain is allowed.
            return;
        }

        if ($this->hostedDomain === $hostedDomain) {
            // Hosted domain is correct.
            return;
        }

        throw HostedDomainException::notMatchingDomain($this->hostedDomain);
    }
}
oauth2-google/src/Exception/HostedDomainException.php000064400000000565147361032630016726 0ustar00<?php

namespace League\OAuth2\Client\Exception;

/**
 * Exception thrown if the Google Provider is configured with a hosted domain that the user doesn't belong to
 */
class HostedDomainException extends \Exception
{

    public static function notMatchingDomain($configuredDomain)
    {
        return new static("User is not part of domain '$configuredDomain'");
    }
}
oauth2-google/README.md000064400000016545147361032630010517 0ustar00# Google Provider for OAuth 2.0 Client

[![Join the chat](https://img.shields.io/badge/gitter-join-1DCE73.svg)](https://gitter.im/thephpleague/oauth2-google)
[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-google.svg)](https://travis-ci.org/thephpleague/oauth2-google)
[![Code Coverage](https://img.shields.io/coveralls/thephpleague/oauth2-google.svg)](https://coveralls.io/r/thephpleague/oauth2-google)
[![Code Quality](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-google.svg)](https://scrutinizer-ci.com/g/thephpleague/oauth2-google/)
[![License](https://img.shields.io/packagist/l/league/oauth2-google.svg)](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE)
[![Latest Stable Version](https://img.shields.io/packagist/v/league/oauth2-google.svg)](https://packagist.org/packages/league/oauth2-google)

This package provides Google OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client).

This package is compliant with [PSR-1][], [PSR-2][] and [PSR-4][]. If you notice compliance oversights, please send
a patch via pull request.

[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md

## Requirements

The following versions of PHP are supported.

* PHP 7.0
* PHP 7.1
* PHP 7.2
* PHP 7.3
* PHP 7.4

This package uses [OpenID Connect][openid-connect] to authenticate users with
Google accounts.

To use this package, it will be necessary to have a Google client ID and client
secret. These are referred to as `{google-client-id}` and `{google-client-secret}`
in the documentation.

Please follow the [Google instructions][oauth-setup] to create the required credentials.

[openid-connect]: https://developers.google.com/identity/protocols/OpenIDConnect
[oauth-setup]: https://developers.google.com/identity/protocols/OpenIDConnect#registeringyourapp

## Installation

To install, use composer:

```sh
composer require league/oauth2-google
```

## Usage

### Authorization Code Flow

```php
use League\OAuth2\Client\Provider\Google;

$provider = new Google([
    'clientId'     => '{google-client-id}',
    'clientSecret' => '{google-client-secret}',
    'redirectUri'  => 'https://example.com/callback-url',
    'hostedDomain' => 'example.com', // optional; used to restrict access to users on your G Suite/Google Apps for Business accounts
]);

if (!empty($_GET['error'])) {

    // Got an error, probably user denied access
    exit('Got error: ' . htmlspecialchars($_GET['error'], ENT_QUOTES, 'UTF-8'));

} elseif (empty($_GET['code'])) {

    // If we don't have an authorization code then get one
    $authUrl = $provider->getAuthorizationUrl();
    $_SESSION['oauth2state'] = $provider->getState();
    header('Location: ' . $authUrl);
    exit;

} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {

    // State is invalid, possible CSRF attack in progress
    unset($_SESSION['oauth2state']);
    exit('Invalid state');

} else {

    // Try to get an access token (using the authorization code grant)
    $token = $provider->getAccessToken('authorization_code', [
        'code' => $_GET['code']
    ]);

    // Optional: Now you have a token you can look up a users profile data
    try {

        // We got an access token, let's now get the owner details
        $ownerDetails = $provider->getResourceOwner($token);

        // Use these details to create a new profile
        printf('Hello %s!', $ownerDetails->getFirstName());

    } catch (Exception $e) {

        // Failed to get user details
        exit('Something went wrong: ' . $e->getMessage());

    }

    // Use this to interact with an API on the users behalf
    echo $token->getToken();

    // Use this to get a new access token if the old one expires
    echo $token->getRefreshToken();

    // Unix timestamp at which the access token expires
    echo $token->getExpires();
}
```

#### Available Options

The `Google` provider has the following [options][auth-params]:

- `accessType` to use online or offline access
- `hostedDomain` to authenticate G Suite users
- `prompt` to modify the prompt that the user will see
- `scopes` to request access to additional user information

[auth-params]: https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters

#### Accessing Token JWT

Google provides a [JSON Web Token][jwt] (JWT) with all access tokens. This token
[contains basic information][openid-jwt] about the authenticated user. The JWT
can be accessed from the `id_token` value of the access token:

```php
/** @var League\OAuth2\Client\Token\AccessToken $token */
$values = $token->getValues();

/** @var string */
$jwt = $values['id_token'];
```

Parsing the JWT will require a [JWT parser][jwt-parsers]. Refer to parser
documentation for instructions.

[jwt]: https://jwt.io/
[openid-jwt]: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo
[jwt-parsers]: https://packagist.org/search/?q=jwt

### Refreshing a Token

Refresh tokens are only provided to applications which request offline access. You can specify offline access by setting the `accessType` option in your provider:

```php
use League\OAuth2\Client\Provider\Google;

$provider = new Google([
    'clientId'     => '{google-client-id}',
    'clientSecret' => '{google-client-secret}',
    'redirectUri'  => 'https://example.com/callback-url',
    'accessType'   => 'offline',
]);
```

It is important to note that the refresh token is only returned on the first request after this it will be `null`. You should securely store the refresh token when it is returned:

```php
$token = $provider->getAccessToken('authorization_code', [
    'code' => $code
]);

// persist the token in a database
$refreshToken = $token->getRefreshToken();
```

If you ever need to get a new refresh token you can request one by forcing the consent prompt:

```php
$authUrl = $provider->getAuthorizationUrl(['prompt' => 'consent']);
```

Now you have everything you need to refresh an access token using a refresh token:

```php
use League\OAuth2\Client\Provider\Google;
use League\OAuth2\Client\Grant\RefreshToken;

$provider = new Google([
    'clientId'     => '{google-client-id}',
    'clientSecret' => '{google-client-secret}',
    'redirectUri'  => 'https://example.com/callback-url',
]);

$grant = new RefreshToken();
$token = $provider->getAccessToken($grant, ['refresh_token' => $refreshToken]);
```

## Scopes

Additional [scopes][scopes] can be set by using the `scope` parameter when
generating the authorization URL:

```php
$authorizationUrl = $provider->getAuthorizationUrl([
    'scope' => [
        'scope-url-here'
    ],
]);
```

[scopes]: https://developers.google.com/identity/protocols/googlescopes

## Testing

Tests can be run with:

```sh
composer test
```

Style checks can be run with:

```sh
composer check
```

## Contributing

Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-google/blob/master/CONTRIBUTING.md) for details.


## Credits

- [Woody Gilk](https://github.com/shadowhand)
- [All Contributors](https://github.com/thephpleague/oauth2-google/contributors)


## License

The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE) for more information.
oauth2-google/CHANGELOG.md000064400000002026147361032630011036 0ustar00OAuth 2.0 Google Provider Changelog

## 3.0.3 - 2020-07-24

### Fixed

- Remove the `approval_prompt` from default parameters, #90

## 3.0.2 - 2019-11-16

### Fixed

- Allow for `family_name` to be undefined in user information, #79 by @majkel89

## 3.0.1 - 2018-12-28

### Fixed

- Correct conflict handling for prompt option, #69 by @mxdpeep

## 3.0.0 - 2018-12-23

### Changed

- Update to latest version of Google OAuth
- Use only OpenID Connect for user details

### Fixed

- Correct handling of selecting from multiple user accounts, #45
- Prevent conflict when using prompt option, #42

### Added

- Add "locale" to user details, #60
- Support additional scopes at construction

### Removed

- Dropped support for Google+ user details, #34 and #63

## 2.2.0 - 2018-03-19

### Added

- Hosted domain validation, #54 by @pradtke

## 2.1.0 - 2018-03-09

### Added

- OpenID Connect support, #48 by @pradtke

## 2.0.0 - 2017-01-24

### Added

- PHP 7.1 support

### Removed

- Dropped PHP 5.5 support

## 1.0.0 - 2015-08-12

- Initial release
oauth2-google/LICENSE000064400000002114147361032630010230 0ustar00The MIT License (MIT)

Copyright (c) 2015 Woody Gilk <woody.gilk@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
plates/CONTRIBUTING.md000064400000002236147361032630010275 0ustar00# Contributing

Contributions are **welcome** and will be fully **credited**.

We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/plates).

## Pull Requests

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow semver. Randomly breaking public APIs is not an option.
- **Create topic branches** - Don't ask us to pull from your master branch.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.

## Running Tests

``` bash
$ phpunit
```
**Happy coding**!plates/src/Extension/URI.php000064400000005427147361032630011764 0ustar00<?php

namespace League\Plates\Extension;

use League\Plates\Engine;
use League\Plates\Template\Template;
use LogicException;

/**
 * Extension that adds a number of URI checks.
 */
class URI implements ExtensionInterface
{
    /**
     * Instance of the current template.
     * @var Template
     */
    public $template;

    /**
     * The request URI.
     * @var string
     */
    protected $uri;

    /**
     * The request URI as an array.
     * @var array
     */
    protected $parts;

    /**
     * Create new URI instance.
     * @param string $uri
     */
    public function __construct($uri)
    {
        $this->uri = $uri;
        $this->parts = explode('/', $this->uri);
    }

    /**
     * Register extension functions.
     * @param Engine $engine
     * @return null
     */
    public function register(Engine $engine)
    {
        $engine->registerFunction('uri', array($this, 'runUri'));
    }

    /**
     * Perform URI check.
     * @param  null|integer|string $var1
     * @param  mixed               $var2
     * @param  mixed               $var3
     * @param  mixed               $var4
     * @return mixed
     */
    public function runUri($var1 = null, $var2 = null, $var3 = null, $var4 = null)
    {
        if (is_null($var1)) {
            return $this->uri;
        }

        if (is_numeric($var1) and is_null($var2)) {
            return array_key_exists($var1, $this->parts) ? $this->parts[$var1] : null;
        }

        if (is_numeric($var1) and is_string($var2)) {
            return $this->checkUriSegmentMatch($var1, $var2, $var3, $var4);
        }

        if (is_string($var1)) {
            return $this->checkUriRegexMatch($var1, $var2, $var3);
        }

        throw new LogicException('Invalid use of the uri function.');
    }

    /**
     * Perform a URI segment match.
     * @param  integer $key
     * @param  string  $string
     * @param  mixed   $returnOnTrue
     * @param  mixed   $returnOnFalse
     * @return mixed
     */
    protected function checkUriSegmentMatch($key, $string, $returnOnTrue = null, $returnOnFalse = null)
    {
        if (array_key_exists($key, $this->parts) && $this->parts[$key] === $string) {
            return is_null($returnOnTrue) ? true : $returnOnTrue;
        }

        return is_null($returnOnFalse) ? false : $returnOnFalse;
    }

    /**
     * Perform a regular express match.
     * @param  string $regex
     * @param  mixed  $returnOnTrue
     * @param  mixed  $returnOnFalse
     * @return mixed
     */
    protected function checkUriRegexMatch($regex, $returnOnTrue = null, $returnOnFalse = null)
    {
        if (preg_match('#^' . $regex . '$#', $this->uri) === 1) {
            return is_null($returnOnTrue) ? true : $returnOnTrue;
        }

        return is_null($returnOnFalse) ? false : $returnOnFalse;
    }
}
plates/src/Extension/ExtensionInterface.php000064400000000303147361032630015106 0ustar00<?php

namespace League\Plates\Extension;

use League\Plates\Engine;

/**
 * A common interface for extensions.
 */
interface ExtensionInterface
{
    public function register(Engine $engine);
}
plates/src/Extension/Asset.php000064400000004010147361032630012367 0ustar00<?php

namespace League\Plates\Extension;

use League\Plates\Engine;
use League\Plates\Template\Template;
use LogicException;

/**
 * Extension that adds the ability to create "cache busted" asset URLs.
 */
class Asset implements ExtensionInterface
{
    /**
     * Instance of the current template.
     * @var Template
     */
    public $template;

    /**
     * Path to asset directory.
     * @var string
     */
    public $path;

    /**
     * Enables the filename method.
     * @var boolean
     */
    public $filenameMethod;

    /**
     * Create new Asset instance.
     * @param string  $path
     * @param boolean $filenameMethod
     */
    public function __construct($path, $filenameMethod = false)
    {
        $this->path = rtrim($path, '/');
        $this->filenameMethod = $filenameMethod;
    }

    /**
     * Register extension function.
     * @param Engine $engine
     * @return null
     */
    public function register(Engine $engine)
    {
        $engine->registerFunction('asset', array($this, 'cachedAssetUrl'));
    }

    /**
     * Create "cache busted" asset URL.
     * @param  string $url
     * @return string
     */
    public function cachedAssetUrl($url)
    {
        $filePath = $this->path . '/' .  ltrim($url, '/');

        if (!file_exists($filePath)) {
            throw new LogicException(
                'Unable to locate the asset "' . $url . '" in the "' . $this->path . '" directory.'
            );
        }

        $lastUpdated = filemtime($filePath);
        $pathInfo = pathinfo($url);

        if ($pathInfo['dirname'] === '.') {
            $directory = '';
        } elseif ($pathInfo['dirname'] === '/') {
            $directory = '/';
        } else {
            $directory = $pathInfo['dirname'] . '/';
        }

        if ($this->filenameMethod) {
            return $directory . $pathInfo['filename'] . '.' . $lastUpdated . '.' . $pathInfo['extension'];
        }

        return $directory . $pathInfo['filename'] . '.' . $pathInfo['extension'] . '?v=' . $lastUpdated;
    }
}
plates/src/Engine.php000064400000013505147361032630010552 0ustar00<?php

namespace League\Plates;

use League\Plates\Extension\ExtensionInterface;
use League\Plates\Template\Data;
use League\Plates\Template\Directory;
use League\Plates\Template\FileExtension;
use League\Plates\Template\Folders;
use League\Plates\Template\Func;
use League\Plates\Template\Functions;
use League\Plates\Template\Name;
use League\Plates\Template\Template;

/**
 * Template API and environment settings storage.
 */
class Engine
{
    /**
     * Default template directory.
     * @var Directory
     */
    protected $directory;

    /**
     * Template file extension.
     * @var FileExtension
     */
    protected $fileExtension;

    /**
     * Collection of template folders.
     * @var Folders
     */
    protected $folders;

    /**
     * Collection of template functions.
     * @var Functions
     */
    protected $functions;

    /**
     * Collection of preassigned template data.
     * @var Data
     */
    protected $data;

    /**
     * Create new Engine instance.
     * @param string $directory
     * @param string $fileExtension
     */
    public function __construct($directory = null, $fileExtension = 'php')
    {
        $this->directory = new Directory($directory);
        $this->fileExtension = new FileExtension($fileExtension);
        $this->folders = new Folders();
        $this->functions = new Functions();
        $this->data = new Data();
    }

    /**
     * Set path to templates directory.
     * @param  string|null $directory Pass null to disable the default directory.
     * @return Engine
     */
    public function setDirectory($directory)
    {
        $this->directory->set($directory);

        return $this;
    }

    /**
     * Get path to templates directory.
     * @return string
     */
    public function getDirectory()
    {
        return $this->directory->get();
    }

    /**
     * Set the template file extension.
     * @param  string|null $fileExtension Pass null to manually set it.
     * @return Engine
     */
    public function setFileExtension($fileExtension)
    {
        $this->fileExtension->set($fileExtension);

        return $this;
    }

    /**
     * Get the template file extension.
     * @return string
     */
    public function getFileExtension()
    {
        return $this->fileExtension->get();
    }

    /**
     * Add a new template folder for grouping templates under different namespaces.
     * @param  string  $name
     * @param  string  $directory
     * @param  boolean $fallback
     * @return Engine
     */
    public function addFolder($name, $directory, $fallback = false)
    {
        $this->folders->add($name, $directory, $fallback);

        return $this;
    }

    /**
     * Remove a template folder.
     * @param  string $name
     * @return Engine
     */
    public function removeFolder($name)
    {
        $this->folders->remove($name);

        return $this;
    }

    /**
     * Get collection of all template folders.
     * @return Folders
     */
    public function getFolders()
    {
        return $this->folders;
    }

    /**
     * Add preassigned template data.
     * @param  array             $data;
     * @param  null|string|array $templates;
     * @return Engine
     */
    public function addData(array $data, $templates = null)
    {
        $this->data->add($data, $templates);

        return $this;
    }

    /**
     * Get all preassigned template data.
     * @param  null|string $template;
     * @return array
     */
    public function getData($template = null)
    {
        return $this->data->get($template);
    }

    /**
     * Register a new template function.
     * @param  string   $name;
     * @param  callback $callback;
     * @return Engine
     */
    public function registerFunction($name, $callback)
    {
        $this->functions->add($name, $callback);

        return $this;
    }

    /**
     * Remove a template function.
     * @param  string $name;
     * @return Engine
     */
    public function dropFunction($name)
    {
        $this->functions->remove($name);

        return $this;
    }

    /**
     * Get a template function.
     * @param  string $name
     * @return Func
     */
    public function getFunction($name)
    {
        return $this->functions->get($name);
    }

    /**
     * Check if a template function exists.
     * @param  string  $name
     * @return boolean
     */
    public function doesFunctionExist($name)
    {
        return $this->functions->exists($name);
    }

    /**
     * Load an extension.
     * @param  ExtensionInterface $extension
     * @return Engine
     */
    public function loadExtension(ExtensionInterface $extension)
    {
        $extension->register($this);

        return $this;
    }

    /**
     * Load multiple extensions.
     * @param  array  $extensions
     * @return Engine
     */
    public function loadExtensions(array $extensions = array())
    {
        foreach ($extensions as $extension) {
            $this->loadExtension($extension);
        }

        return $this;
    }

    /**
     * Get a template path.
     * @param  string $name
     * @return string
     */
    public function path($name)
    {
        $name = new Name($this, $name);

        return $name->getPath();
    }

    /**
     * Check if a template exists.
     * @param  string  $name
     * @return boolean
     */
    public function exists($name)
    {
        $name = new Name($this, $name);

        return $name->doesPathExist();
    }

    /**
     * Create a new template.
     * @param  string   $name
     * @return Template
     */
    public function make($name)
    {
        return new Template($this, $name);
    }

    /**
     * Create a new template and render it.
     * @param  string $name
     * @param  array  $data
     * @return string
     */
    public function render($name, array $data = array())
    {
        return $this->make($name)->render($data);
    }
}
plates/src/Template/Folders.php000064400000003036147361032630012514 0ustar00<?php

namespace League\Plates\Template;

use LogicException;

/**
 * A collection of template folders.
 */
class Folders
{
    /**
     * Array of template folders.
     * @var array
     */
    protected $folders = array();

    /**
     * Add a template folder.
     * @param  string  $name
     * @param  string  $path
     * @param  boolean $fallback
     * @return Folders
     */
    public function add($name, $path, $fallback = false)
    {
        if ($this->exists($name)) {
            throw new LogicException('The template folder "' . $name . '" is already being used.');
        }

        $this->folders[$name] = new Folder($name, $path, $fallback);

        return $this;
    }

    /**
     * Remove a template folder.
     * @param  string  $name
     * @return Folders
     */
    public function remove($name)
    {
        if (!$this->exists($name)) {
            throw new LogicException('The template folder "' . $name . '" was not found.');
        }

        unset($this->folders[$name]);

        return $this;
    }

    /**
     * Get a template folder.
     * @param  string $name
     * @return Folder
     */
    public function get($name)
    {
        if (!$this->exists($name)) {
            throw new LogicException('The template folder "' . $name . '" was not found.');
        }

        return $this->folders[$name];
    }

    /**
     * Check if a template folder exists.
     * @param  string  $name
     * @return boolean
     */
    public function exists($name)
    {
        return isset($this->folders[$name]);
    }
}
plates/src/Template/FileExtension.php000064400000001451147361032630013671 0ustar00<?php

namespace League\Plates\Template;

/**
 * Template file extension.
 */
class FileExtension
{
    /**
     * Template file extension.
     * @var string
     */
    protected $fileExtension;

    /**
     * Create new FileExtension instance.
     * @param null|string $fileExtension
     */
    public function __construct($fileExtension = 'php')
    {
        $this->set($fileExtension);
    }

    /**
     * Set the template file extension.
     * @param  null|string   $fileExtension
     * @return FileExtension
     */
    public function set($fileExtension)
    {
        $this->fileExtension = $fileExtension;

        return $this;
    }

    /**
     * Get the template file extension.
     * @return string
     */
    public function get()
    {
        return $this->fileExtension;
    }
}
plates/src/Template/Name.php000064400000010172147361032630011775 0ustar00<?php

namespace League\Plates\Template;

use League\Plates\Engine;
use LogicException;

/**
 * A template name.
 */
class Name
{
    /**
     * Instance of the template engine.
     * @var Engine
     */
    protected $engine;

    /**
     * The original name.
     * @var string
     */
    protected $name;

    /**
     * The parsed template folder.
     * @var Folder
     */
    protected $folder;

    /**
     * The parsed template filename.
     * @var string
     */
    protected $file;

    /**
     * Create a new Name instance.
     * @param Engine $engine
     * @param string $name
     */
    public function __construct(Engine $engine, $name)
    {
        $this->setEngine($engine);
        $this->setName($name);
    }

    /**
     * Set the engine.
     * @param  Engine $engine
     * @return Name
     */
    public function setEngine(Engine $engine)
    {
        $this->engine = $engine;

        return $this;
    }

    /**
     * Get the engine.
     * @return Engine
     */
    public function getEngine()
    {
        return $this->engine;
    }

    /**
     * Set the original name and parse it.
     * @param  string $name
     * @return Name
     */
    public function setName($name)
    {
        $this->name = $name;

        $parts = explode('::', $this->name);

        if (count($parts) === 1) {
            $this->setFile($parts[0]);
        } elseif (count($parts) === 2) {
            $this->setFolder($parts[0]);
            $this->setFile($parts[1]);
        } else {
            throw new LogicException(
                'The template name "' . $this->name . '" is not valid. ' .
                'Do not use the folder namespace separator "::" more than once.'
            );
        }

        return $this;
    }

    /**
     * Get the original name.
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set the parsed template folder.
     * @param  string $folder
     * @return Name
     */
    public function setFolder($folder)
    {
        $this->folder = $this->engine->getFolders()->get($folder);

        return $this;
    }

    /**
     * Get the parsed template folder.
     * @return string
     */
    public function getFolder()
    {
        return $this->folder;
    }

    /**
     * Set the parsed template file.
     * @param  string $file
     * @return Name
     */
    public function setFile($file)
    {
        if ($file === '') {
            throw new LogicException(
                'The template name "' . $this->name . '" is not valid. ' .
                'The template name cannot be empty.'
            );
        }

        $this->file = $file;

        if (!is_null($this->engine->getFileExtension())) {
            $this->file .= '.' . $this->engine->getFileExtension();
        }

        return $this;
    }

    /**
     * Get the parsed template file.
     * @return string
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * Resolve template path.
     * @return string
     */
    public function getPath()
    {
        if (is_null($this->folder)) {
            return $this->getDefaultDirectory() . DIRECTORY_SEPARATOR . $this->file;
        }

        $path = $this->folder->getPath() . DIRECTORY_SEPARATOR . $this->file;

        if (!is_file($path) and $this->folder->getFallback() and is_file($this->getDefaultDirectory() . DIRECTORY_SEPARATOR . $this->file)) {
            $path = $this->getDefaultDirectory() . DIRECTORY_SEPARATOR . $this->file;
        }

        return $path;
    }

    /**
     * Check if template path exists.
     * @return boolean
     */
    public function doesPathExist()
    {
        return is_file($this->getPath());
    }

    /**
     * Get the default templates directory.
     * @return string
     */
    protected function getDefaultDirectory()
    {
        $directory = $this->engine->getDirectory();

        if (is_null($directory)) {
            throw new LogicException(
                'The template name "' . $this->name . '" is not valid. '.
                'The default directory has not been defined.'
            );
        }

        return $directory;
    }
}
plates/src/Template/Directory.php000064400000001677147361032630013073 0ustar00<?php

namespace League\Plates\Template;

use LogicException;

/**
 * Default template directory.
 */
class Directory
{
    /**
     * Template directory path.
     * @var string
     */
    protected $path;

    /**
     * Create new Directory instance.
     * @param string $path
     */
    public function __construct($path = null)
    {
        $this->set($path);
    }

    /**
     * Set path to templates directory.
     * @param  string|null $path Pass null to disable the default directory.
     * @return Directory
     */
    public function set($path)
    {
        if (!is_null($path) and !is_dir($path)) {
            throw new LogicException(
                'The specified path "' . $path . '" does not exist.'
            );
        }

        $this->path = $path;

        return $this;
    }

    /**
     * Get path to templates directory.
     * @return string
     */
    public function get()
    {
        return $this->path;
    }
}
plates/src/Template/Folder.php000064400000003524147361032630012333 0ustar00<?php

namespace League\Plates\Template;

use LogicException;

/**
 * A template folder.
 */
class Folder
{
    /**
     * The folder name.
     * @var string
     */
    protected $name;

    /**
     * The folder path.
     * @var string
     */
    protected $path;

    /**
     * The folder fallback status.
     * @var boolean
     */
    protected $fallback;

    /**
     * Create a new Folder instance.
     * @param string  $name
     * @param string  $path
     * @param boolean $fallback
     */
    public function __construct($name, $path, $fallback = false)
    {
        $this->setName($name);
        $this->setPath($path);
        $this->setFallback($fallback);
    }

    /**
     * Set the folder name.
     * @param  string $name
     * @return Folder
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get the folder name.
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set the folder path.
     * @param  string $path
     * @return Folder
     */
    public function setPath($path)
    {
        if (!is_dir($path)) {
            throw new LogicException('The specified directory path "' . $path . '" does not exist.');
        }

        $this->path = $path;

        return $this;
    }

    /**
     * Get the folder path.
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Set the folder fallback status.
     * @param  boolean $fallback
     * @return Folder
     */
    public function setFallback($fallback)
    {
        $this->fallback = $fallback;

        return $this;
    }

    /**
     * Get the folder fallback status.
     * @return boolean
     */
    public function getFallback()
    {
        return $this->fallback;
    }
}
plates/src/Template/Data.php000064400000004254147361032630011772 0ustar00<?php

namespace League\Plates\Template;

use LogicException;

/**
 * Preassigned template data.
 */
class Data
{
    /**
     * Variables shared by all templates.
     * @var array
     */
    protected $sharedVariables = array();

    /**
     * Specific template variables.
     * @var array
     */
    protected $templateVariables = array();

    /**
     * Add template data.
     * @param  array             $data;
     * @param  null|string|array $templates;
     * @return Data
     */
    public function add(array $data, $templates = null)
    {
        if (is_null($templates)) {
            return $this->shareWithAll($data);
        }

        if (is_array($templates)) {
            return $this->shareWithSome($data, $templates);
        }

        if (is_string($templates)) {
            return $this->shareWithSome($data, array($templates));
        }

        throw new LogicException(
            'The templates variable must be null, an array or a string, ' . gettype($templates) . ' given.'
        );
    }

    /**
     * Add data shared with all templates.
     * @param  array $data;
     * @return Data
     */
    public function shareWithAll($data)
    {
        $this->sharedVariables = array_merge($this->sharedVariables, $data);

        return $this;
    }

    /**
     * Add data shared with some templates.
     * @param  array $data;
     * @param  array $templates;
     * @return Data
     */
    public function shareWithSome($data, array $templates)
    {
        foreach ($templates as $template) {
            if (isset($this->templateVariables[$template])) {
                $this->templateVariables[$template] = array_merge($this->templateVariables[$template], $data);
            } else {
                $this->templateVariables[$template] = $data;
            }
        }

        return $this;
    }

    /**
     * Get template data.
     * @param  null|string $template;
     * @return array
     */
    public function get($template = null)
    {
        if (isset($template, $this->templateVariables[$template])) {
            return array_merge($this->sharedVariables, $this->templateVariables[$template]);
        }

        return $this->sharedVariables;
    }
}
plates/src/Template/Functions.php000064400000003127147361032630013067 0ustar00<?php

namespace League\Plates\Template;

use LogicException;

/**
 * A collection of template functions.
 */
class Functions
{
    /**
     * Array of template functions.
     * @var array
     */
    protected $functions = array();

    /**
     * Add a new template function.
     * @param  string    $name;
     * @param  callback  $callback;
     * @return Functions
     */
    public function add($name, $callback)
    {
        if ($this->exists($name)) {
            throw new LogicException(
                'The template function name "' . $name . '" is already registered.'
            );
        }

        $this->functions[$name] = new Func($name, $callback);

        return $this;
    }

    /**
     * Remove a template function.
     * @param  string    $name;
     * @return Functions
     */
    public function remove($name)
    {
        if (!$this->exists($name)) {
            throw new LogicException(
                'The template function "' . $name . '" was not found.'
            );
        }

        unset($this->functions[$name]);

        return $this;
    }

    /**
     * Get a template function.
     * @param  string $name
     * @return Func
     */
    public function get($name)
    {
        if (!$this->exists($name)) {
            throw new LogicException('The template function "' . $name . '" was not found.');
        }

        return $this->functions[$name];
    }

    /**
     * Check if a template function exists.
     * @param  string  $name
     * @return boolean
     */
    public function exists($name)
    {
        return isset($this->functions[$name]);
    }
}
plates/src/Template/Template.php000064400000017241147361032630012674 0ustar00<?php

namespace League\Plates\Template;

use Exception;
use League\Plates\Engine;
use LogicException;
use Throwable;

/**
 * Container which holds template data and provides access to template functions.
 */
class Template
{
    /**
     * Instance of the template engine.
     * @var Engine
     */
    protected $engine;

    /**
     * The name of the template.
     * @var Name
     */
    protected $name;

    /**
     * The data assigned to the template.
     * @var array
     */
    protected $data = array();

    /**
     * An array of section content.
     * @var array
     */
    protected $sections = array();

    /**
     * The name of the section currently being rendered.
     * @var string
     */
    protected $sectionName;

    /**
     * Whether the section should be appended or not.
     * @var boolean
     */
    protected $appendSection;

    /**
     * The name of the template layout.
     * @var string
     */
    protected $layoutName;

    /**
     * The data assigned to the template layout.
     * @var array
     */
    protected $layoutData;

    /**
     * Create new Template instance.
     * @param Engine $engine
     * @param string $name
     */
    public function __construct(Engine $engine, $name)
    {
        $this->engine = $engine;
        $this->name = new Name($engine, $name);

        $this->data($this->engine->getData($name));
    }

    /**
     * Magic method used to call extension functions.
     * @param  string $name
     * @param  array  $arguments
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        return $this->engine->getFunction($name)->call($this, $arguments);
    }

    /**
     * Alias for render() method.
     * @throws \Throwable
     * @throws \Exception
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * Assign or get template data.
     * @param  array $data
     * @return mixed
     */
    public function data(array $data = null)
    {
        if (is_null($data)) {
            return $this->data;
        }

        $this->data = array_merge($this->data, $data);
    }

    /**
     * Check if the template exists.
     * @return boolean
     */
    public function exists()
    {
        return $this->name->doesPathExist();
    }

    /**
     * Get the template path.
     * @return string
     */
    public function path()
    {
        return $this->name->getPath();
    }

    /**
     * Render the template and layout.
     * @param  array  $data
     * @throws \Throwable
     * @throws \Exception
     * @return string
     */
    public function render(array $data = array())
    {
        $this->data($data);
        unset($data);
        extract($this->data);

        if (!$this->exists()) {
            throw new LogicException(
                'The template "' . $this->name->getName() . '" could not be found at "' . $this->path() . '".'
            );
        }

        try {
            $level = ob_get_level();
            ob_start();

            include $this->path();

            $content = ob_get_clean();

            if (isset($this->layoutName)) {
                $layout = $this->engine->make($this->layoutName);
                $layout->sections = array_merge($this->sections, array('content' => $content));
                $content = $layout->render($this->layoutData);
            }

            return $content;
        } catch (Throwable $e) {
            while (ob_get_level() > $level) {
                ob_end_clean();
            }

            throw $e;
        } catch (Exception $e) {
            while (ob_get_level() > $level) {
                ob_end_clean();
            }

            throw $e;
        }
    }

    /**
     * Set the template's layout.
     * @param  string $name
     * @param  array  $data
     * @return null
     */
    public function layout($name, array $data = array())
    {
        $this->layoutName = $name;
        $this->layoutData = $data;
    }

    /**
     * Start a new section block.
     * @param  string  $name
     * @return null
     */
    public function start($name)
    {
        if ($name === 'content') {
            throw new LogicException(
                'The section name "content" is reserved.'
            );
        }

        if ($this->sectionName) {
            throw new LogicException('You cannot nest sections within other sections.');
        }

        $this->sectionName = $name;

        ob_start();
    }

    /**
     * Start a new append section block.
     * @param  string $name
     * @return null
     */
    public function push($name)
    {
        $this->appendSection = true;

        $this->start($name);
    }

    /**
     * Stop the current section block.
     * @return null
     */
    public function stop()
    {
        if (is_null($this->sectionName)) {
            throw new LogicException(
                'You must start a section before you can stop it.'
            );
        }

        if (!isset($this->sections[$this->sectionName])) {
            $this->sections[$this->sectionName] = '';
        }

        $this->sections[$this->sectionName] = $this->appendSection ? $this->sections[$this->sectionName] . ob_get_clean() : ob_get_clean();
        $this->sectionName = null;
        $this->appendSection = false;
    }

    /**
     * Alias of stop().
     * @return null
     */
    public function end()
    {
        $this->stop();
    }

    /**
     * Returns the content for a section block.
     * @param  string      $name    Section name
     * @param  string      $default Default section content
     * @return string|null
     */
    public function section($name, $default = null)
    {
        if (!isset($this->sections[$name])) {
            return $default;
        }

        return $this->sections[$name];
    }

    /**
     * Fetch a rendered template.
     * @param  string $name
     * @param  array  $data
     * @return string
     */
    public function fetch($name, array $data = array())
    {
        return $this->engine->render($name, $data);
    }

    /**
     * Output a rendered template.
     * @param  string $name
     * @param  array  $data
     * @return null
     */
    public function insert($name, array $data = array())
    {
        echo $this->engine->render($name, $data);
    }

    /**
     * Apply multiple functions to variable.
     * @param  mixed  $var
     * @param  string $functions
     * @return mixed
     */
    public function batch($var, $functions)
    {
        foreach (explode('|', $functions) as $function) {
            if ($this->engine->doesFunctionExist($function)) {
                $var = call_user_func(array($this, $function), $var);
            } elseif (is_callable($function)) {
                $var = call_user_func($function, $var);
            } else {
                throw new LogicException(
                    'The batch function could not find the "' . $function . '" function.'
                );
            }
        }

        return $var;
    }

    /**
     * Escape string.
     * @param  string      $string
     * @param  null|string $functions
     * @return string
     */
    public function escape($string, $functions = null)
    {
        static $flags;

        if (!isset($flags)) {
            $flags = ENT_QUOTES | (defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0);
        }

        if ($functions) {
            $string = $this->batch($string, $functions);
        }

        return htmlspecialchars($string, $flags, 'UTF-8');
    }

    /**
     * Alias to escape function.
     * @param  string      $string
     * @param  null|string $functions
     * @return string
     */
    public function e($string, $functions = null)
    {
        return $this->escape($string, $functions);
    }
}
plates/src/Template/Func.php000064400000004137147361032630012014 0ustar00<?php

namespace League\Plates\Template;

use League\Plates\Extension\ExtensionInterface;
use LogicException;

/**
 * A template function.
 */
class Func
{
    /**
     * The function name.
     * @var string
     */
    protected $name;

    /**
     * The function callback.
     * @var callable
     */
    protected $callback;

    /**
     * Create new Func instance.
     * @param string   $name
     * @param callable $callback
     */
    public function __construct($name, $callback)
    {
        $this->setName($name);
        $this->setCallback($callback);
    }

    /**
     * Set the function name.
     * @param  string $name
     * @return Func
     */
    public function setName($name)
    {
        if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name) !== 1) {
            throw new LogicException(
                'Not a valid function name.'
            );
        }

        $this->name = $name;

        return $this;
    }

    /**
     * Get the function name.
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set the function callback
     * @param  callable $callback
     * @return Func
     */
    public function setCallback($callback)
    {
        if (!is_callable($callback, true)) {
            throw new LogicException(
                'Not a valid function callback.'
            );
        }

        $this->callback = $callback;

        return $this;
    }

    /**
     * Get the function callback.
     * @return callable
     */
    public function getCallback()
    {
        return $this->callback;
    }

    /**
     * Call the function.
     * @param  Template $template
     * @param  array    $arguments
     * @return mixed
     */
    public function call(Template $template = null, $arguments = array())
    {
        if (is_array($this->callback) and
            isset($this->callback[0]) and
            $this->callback[0] instanceof ExtensionInterface
        ) {
            $this->callback[0]->template = $template;
        }

        return call_user_func_array($this->callback, $arguments);
    }
}
plates/README.md000064400000006305147361032630007324 0ustar00Plates
======

[![Author](http://img.shields.io/badge/author-@reinink-blue.svg?style=flat-square)](https://twitter.com/reinink)
[![Source Code](http://img.shields.io/badge/source-league/plates-blue.svg?style=flat-square)](https://github.com/thephpleague/plates)
[![Latest Version](https://img.shields.io/github/release/thephpleague/plates.svg?style=flat-square)](https://github.com/thephpleague/plates/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/travis/thephpleague/plates/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/plates)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/plates.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/plates/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/plates.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/plates)
[![Total Downloads](https://img.shields.io/packagist/dt/league/plates.svg?style=flat-square)](https://packagist.org/packages/league/plates)

Plates is a native PHP template system that's fast, easy to use and easy to extend. It's inspired by the excellent [Twig](http://twig.sensiolabs.org/) template engine and strives to bring modern template language functionality to native PHP templates. Plates is designed for developers who prefer to use native PHP templates over compiled template languages, such as Twig or Smarty.

### Highlights

- Native PHP templates, no new [syntax](http://platesphp.com/templates/syntax/) to learn
- Plates is a template system, not a template language
- Plates encourages the use of existing PHP functions
- Increase code reuse with template [layouts](http://platesphp.com/templates/layouts/) and [inheritance](http://platesphp.com/templates/inheritance/)
- Template [folders](http://platesphp.com/engine/folders/) for grouping templates into namespaces
- [Data](http://platesphp.com/templates/data/#preassigned-and-shared-data) sharing across templates
- Preassign [data](http://platesphp.com/templates/data/#preassigned-and-shared-data) to specific templates
- Built-in [escaping](http://platesphp.com/templates/escaping/) helpers
- Easy to extend using [functions](http://platesphp.com/engine/functions/) and [extensions](http://platesphp.com/engine/extensions/)
- Framework-agnostic, will work with any project
- Decoupled design makes templates easy to test
- Composer ready and PSR-2 compliant

## Installation

Plates is available via Composer:

```
composer require league/plates
```

## Documentation

Full documentation can be found at [platesphp.com](http://platesphp.com/).

## Testing

```bash
phpunit
```

## Contributing

Please see [CONTRIBUTING](https://github.com/thephpleague/plates/blob/master/CONTRIBUTING.md) for details.

## Security

If you discover any security related issues, please email jonathan@reinink.ca instead of using the issue tracker.

## Credits

- [Jonathan Reinink](https://github.com/reinink)
- [All Contributors](https://github.com/thephpleague/plates/contributors)

## License

The MIT License (MIT). Please see [License File](https://github.com/thephpleague/plates/blob/master/LICENSE) for more information.
plates/example/example.php000064400000000461147361032630011641 0ustar00<?php

include '../vendor/autoload.php';

// Create new Plates instance
$templates = new League\Plates\Engine('templates');

// Preassign data to the layout
$templates->addData(['company' => 'The Company Name'], 'layout');

// Render a template
echo $templates->render('profile', ['name' => 'Jonathan']);
plates/example/templates/profile.php000064400000000410147361032630013636 0ustar00<?php $this->layout('layout', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->e($name)?>!</p>

<?php $this->insert('sidebar') ?>

<?php $this->push('scripts') ?>
    <script>
        // Some JavaScript
    </script>
<?php $this->end() ?>plates/example/templates/layout.php000064400000000260147361032630013516 0ustar00<html>
<head>
    <title><?=$this->e($title)?> | <?=$this->e($company)?></title>
</head>
<body>

<?=$this->section('content')?>

<?=$this->section('scripts')?>

</body>
</html>plates/example/templates/sidebar.php000064400000000342147361032630013613 0ustar00<ul>
    <li><a href="#link">Example sidebar link</a></li>
    <li><a href="#link">Example sidebar link</a></li>
    <li><a href="#link">Example sidebar link</a></li>
    <li><a href="#link">Example sidebar link</a></li>
</ul>plates/LICENSE000064400000002116147361032630007046 0ustar00The MIT License (MIT)

Copyright (c) 2013 The League of Extraordinary Packages

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.flysystem-aws-s3-v3/src/AwsS3Adapter.php000064400000042361147361032630014020 0ustar00<?php

namespace League\Flysystem\AwsS3v3;

use Aws\Result;
use Aws\S3\Exception\DeleteMultipleObjectsException;
use Aws\S3\Exception\S3Exception;
use Aws\S3\Exception\S3MultipartUploadException;
use Aws\S3\S3ClientInterface;
use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\CanOverwriteFiles;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\Util;

class AwsS3Adapter extends AbstractAdapter implements CanOverwriteFiles
{
    const PUBLIC_GRANT_URI = 'http://acs.amazonaws.com/groups/global/AllUsers';

    /**
     * @var array
     */
    protected static $resultMap = [
        'Body'          => 'contents',
        'ContentLength' => 'size',
        'ContentType'   => 'mimetype',
        'Size'          => 'size',
        'Metadata'      => 'metadata',
        'StorageClass'  => 'storageclass',
        'ETag'          => 'etag',
        'VersionId'     => 'versionid'
    ];

    /**
     * @var array
     */
    protected static $metaOptions = [
        'ACL',
        'CacheControl',
        'ContentDisposition',
        'ContentEncoding',
        'ContentLength',
        'ContentType',
        'Expires',
        'GrantFullControl',
        'GrantRead',
        'GrantReadACP',
        'GrantWriteACP',
        'Metadata',
        'RequestPayer',
        'SSECustomerAlgorithm',
        'SSECustomerKey',
        'SSECustomerKeyMD5',
        'SSEKMSKeyId',
        'ServerSideEncryption',
        'StorageClass',
        'Tagging',
        'WebsiteRedirectLocation',
    ];

    /**
     * @var S3ClientInterface
     */
    protected $s3Client;

    /**
     * @var string
     */
    protected $bucket;

    /**
     * @var array
     */
    protected $options = [];

    /**
     * Constructor.
     *
     * @param S3ClientInterface $client
     * @param string   $bucket
     * @param string   $prefix
     * @param array    $options
     */
    public function __construct(S3ClientInterface $client, $bucket, $prefix = '', array $options = [])
    {
        $this->s3Client = $client;
        $this->bucket = $bucket;
        $this->setPathPrefix($prefix);
        $this->options = $options;
    }

    /**
     * Get the S3Client bucket.
     *
     * @return string
     */
    public function getBucket()
    {
        return $this->bucket;
    }

    /**
     * Set the S3Client bucket.
     *
     * @return string
     */
    public function setBucket($bucket)
    {
        $this->bucket = $bucket;
    }

    /**
     * Get the S3Client instance.
     *
     * @return S3ClientInterface
     */
    public function getClient()
    {
        return $this->s3Client;
    }

    /**
     * Write a new file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config Config object
     *
     * @return false|array false on failure file meta data on success
     */
    public function write($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, $config);
    }

    /**
     * Update a file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config Config object
     *
     * @return false|array false on failure file meta data on success
     */
    public function update($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, $config);
    }

    /**
     * Rename a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function rename($path, $newpath)
    {
        if ( ! $this->copy($path, $newpath)) {
            return false;
        }

        return $this->delete($path);
    }

    /**
     * Delete a file.
     *
     * @param string $path
     *
     * @return bool
     */
    public function delete($path)
    {
        $location = $this->applyPathPrefix($path);

        $command = $this->s3Client->getCommand(
            'deleteObject',
            [
                'Bucket' => $this->bucket,
                'Key'    => $location,
            ]
        );

        $this->s3Client->execute($command);

        return ! $this->has($path);
    }

    /**
     * Delete a directory.
     *
     * @param string $dirname
     *
     * @return bool
     */
    public function deleteDir($dirname)
    {
        try {
            $prefix = $this->applyPathPrefix($dirname) . '/';
            $this->s3Client->deleteMatchingObjects($this->bucket, $prefix);
        } catch (DeleteMultipleObjectsException $exception) {
            return false;
        }

        return true;
    }

    /**
     * Create a directory.
     *
     * @param string $dirname directory name
     * @param Config $config
     *
     * @return bool|array
     */
    public function createDir($dirname, Config $config)
    {
        return $this->upload($dirname . '/', '', $config);
    }

    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return bool
     */
    public function has($path)
    {
        $location = $this->applyPathPrefix($path);

        if ($this->s3Client->doesObjectExist($this->bucket, $location, $this->options)) {
            return true;
        }

        return $this->doesDirectoryExist($location);
    }

    /**
     * Read a file.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function read($path)
    {
        $response = $this->readObject($path);

        if ($response !== false) {
            $response['contents'] = $response['contents']->getContents();
        }

        return $response;
    }

    /**
     * List contents of a directory.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    public function listContents($directory = '', $recursive = false)
    {
        $prefix = $this->applyPathPrefix(rtrim($directory, '/') . '/');
        $options = ['Bucket' => $this->bucket, 'Prefix' => ltrim($prefix, '/')];

        if ($recursive === false) {
            $options['Delimiter'] = '/';
        }

        $listing = $this->retrievePaginatedListing($options);
        $normalizer = [$this, 'normalizeResponse'];
        $normalized = array_map($normalizer, $listing);

        return Util::emulateDirectories($normalized);
    }

    /**
     * @param array $options
     *
     * @return array
     */
    protected function retrievePaginatedListing(array $options)
    {
        $resultPaginator = $this->s3Client->getPaginator('ListObjects', $options);
        $listing = [];

        foreach ($resultPaginator as $result) {
            $listing = array_merge($listing, $result->get('Contents') ?: [], $result->get('CommonPrefixes') ?: []);
        }

        return $listing;
    }

    /**
     * Get all the meta data of a file or directory.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getMetadata($path)
    {
        $command = $this->s3Client->getCommand(
            'headObject',
            [
                'Bucket' => $this->bucket,
                'Key'    => $this->applyPathPrefix($path),
            ] + $this->options
        );

        /* @var Result $result */
        try {
            $result = $this->s3Client->execute($command);
        } catch (S3Exception $exception) {
            if ($this->is404Exception($exception)) {
                return false;
            }

            throw $exception;
        }

        return $this->normalizeResponse($result->toArray(), $path);
    }

    /**
     * @return bool
     */
    private function is404Exception(S3Exception $exception)
    {
        $response = $exception->getResponse();

        if ($response !== null && $response->getStatusCode() === 404) {
            return true;
        }

        return false;
    }

    /**
     * Get all the meta data of a file or directory.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Get the mimetype of a file.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getMimetype($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Get the timestamp of a file.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Write a new file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function writeStream($path, $resource, Config $config)
    {
        return $this->upload($path, $resource, $config);
    }

    /**
     * Update a file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->upload($path, $resource, $config);
    }

    /**
     * Copy a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function copy($path, $newpath)
    {
        $command = $this->s3Client->getCommand(
            'copyObject',
            [
                'Bucket'     => $this->bucket,
                'Key'        => $this->applyPathPrefix($newpath),
                'CopySource' => rawurlencode($this->bucket . '/' . $this->applyPathPrefix($path)),
                'ACL'        => $this->getRawVisibility($path) === AdapterInterface::VISIBILITY_PUBLIC
                    ? 'public-read' : 'private',
            ] + $this->options
        );

        try {
            $this->s3Client->execute($command);
        } catch (S3Exception $e) {
            return false;
        }

        return true;
    }

    /**
     * Read a file as a stream.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function readStream($path)
    {
        $response = $this->readObject($path);

        if ($response !== false) {
            $response['stream'] = $response['contents']->detach();
            unset($response['contents']);
        }

        return $response;
    }

    /**
     * Read an object and normalize the response.
     *
     * @param string $path
     *
     * @return array|bool
     */
    protected function readObject($path)
    {
        $options = [
            'Bucket' => $this->bucket,
            'Key'    => $this->applyPathPrefix($path),
        ];

        if (isset($this->options['@http'])) {
            $options['@http'] = $this->options['@http'];
        }

        $command = $this->s3Client->getCommand('getObject', $options + $this->options);

        try {
            /** @var Result $response */
            $response = $this->s3Client->execute($command);
        } catch (S3Exception $e) {
            return false;
        }

        return $this->normalizeResponse($response->toArray(), $path);
    }

    /**
     * Set the visibility for a file.
     *
     * @param string $path
     * @param string $visibility
     *
     * @return array|false file meta data
     */
    public function setVisibility($path, $visibility)
    {
        $command = $this->s3Client->getCommand(
            'putObjectAcl',
            [
                'Bucket' => $this->bucket,
                'Key'    => $this->applyPathPrefix($path),
                'ACL'    => $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'public-read' : 'private',
            ]
        );

        try {
            $this->s3Client->execute($command);
        } catch (S3Exception $exception) {
            return false;
        }

        return compact('path', 'visibility');
    }

    /**
     * Get the visibility of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getVisibility($path)
    {
        return ['visibility' => $this->getRawVisibility($path)];
    }

    /**
     * {@inheritdoc}
     */
    public function applyPathPrefix($path)
    {
        return ltrim(parent::applyPathPrefix($path), '/');
    }

    /**
     * {@inheritdoc}
     */
    public function setPathPrefix($prefix)
    {
        $prefix = ltrim($prefix, '/');

        return parent::setPathPrefix($prefix);
    }

    /**
     * Get the object acl presented as a visibility.
     *
     * @param string $path
     *
     * @return string
     */
    protected function getRawVisibility($path)
    {
        $command = $this->s3Client->getCommand(
            'getObjectAcl',
            [
                'Bucket' => $this->bucket,
                'Key'    => $this->applyPathPrefix($path),
            ]
        );

        $result = $this->s3Client->execute($command);
        $visibility = AdapterInterface::VISIBILITY_PRIVATE;

        foreach ($result->get('Grants') as $grant) {
            if (
                isset($grant['Grantee']['URI'])
                && $grant['Grantee']['URI'] === self::PUBLIC_GRANT_URI
                && $grant['Permission'] === 'READ'
            ) {
                $visibility = AdapterInterface::VISIBILITY_PUBLIC;
                break;
            }
        }

        return $visibility;
    }

    /**
     * Upload an object.
     *
     * @param string          $path
     * @param string|resource $body
     * @param Config          $config
     *
     * @return array|bool
     */
    protected function upload($path, $body, Config $config)
    {
        $key = $this->applyPathPrefix($path);
        $options = $this->getOptionsFromConfig($config);
        $acl = array_key_exists('ACL', $options) ? $options['ACL'] : 'private';

        if (!$this->isOnlyDir($path)) {
            if ( ! isset($options['ContentType'])) {
                $options['ContentType'] = Util::guessMimeType($path, $body);
            }

            if ( ! isset($options['ContentLength'])) {
                $options['ContentLength'] = is_resource($body) ? Util::getStreamSize($body) : Util::contentSize($body);
            }

            if ($options['ContentLength'] === null) {
                unset($options['ContentLength']);
            }
        }

        try {
            $this->s3Client->upload($this->bucket, $key, $body, $acl, ['params' => $options]);
        } catch (S3MultipartUploadException $multipartUploadException) {
            return false;
        }

        return $this->normalizeResponse($options, $path);
    }

    /**
     * Check if the path contains only directories
     *
     * @param string $path
     *
     * @return bool
     */
    private function isOnlyDir($path)
    {
        return substr($path, -1) === '/';
    }

    /**
     * Get options from the config.
     *
     * @param Config $config
     *
     * @return array
     */
    protected function getOptionsFromConfig(Config $config)
    {
        $options = $this->options;

        if ($visibility = $config->get('visibility')) {
            // For local reference
            $options['visibility'] = $visibility;
            // For external reference
            $options['ACL'] = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'public-read' : 'private';
        }

        if ($mimetype = $config->get('mimetype')) {
            // For local reference
            $options['mimetype'] = $mimetype;
            // For external reference
            $options['ContentType'] = $mimetype;
        }

        foreach (static::$metaOptions as $option) {
            if ( ! $config->has($option)) {
                continue;
            }
            $options[$option] = $config->get($option);
        }

        return $options;
    }

    /**
     * Normalize the object result array.
     *
     * @param array  $response
     * @param string $path
     *
     * @return array
     */
    protected function normalizeResponse(array $response, $path = null)
    {
        $result = [
            'path' => $path ?: $this->removePathPrefix(
                isset($response['Key']) ? $response['Key'] : $response['Prefix']
            ),
        ];
        $result = array_merge($result, Util::pathinfo($result['path']));

        if (isset($response['LastModified'])) {
            $result['timestamp'] = strtotime($response['LastModified']);
        }

        if ($this->isOnlyDir($result['path'])) {
            $result['type'] = 'dir';
            $result['path'] = rtrim($result['path'], '/');

            return $result;
        }

        return array_merge($result, Util::map($response, static::$resultMap), ['type' => 'file']);
    }

    /**
     * @param string $location
     *
     * @return bool
     */
    protected function doesDirectoryExist($location)
    {
        // Maybe this isn't an actual key, but a prefix.
        // Do a prefix listing of objects to determine.
        $command = $this->s3Client->getCommand(
            'listObjects',
            [
                'Bucket'  => $this->bucket,
                'Prefix'  => rtrim($location, '/') . '/',
                'MaxKeys' => 1,
            ]
        );

        try {
            $result = $this->s3Client->execute($command);

            return $result['Contents'] || $result['CommonPrefixes'];
        } catch (S3Exception $e) {
            if ($e->getStatusCode() === 403) {
                return false;
            }

            throw $e;
        }
    }
}
flysystem-aws-s3-v3/LICENSE000064400000002076147361032630011263 0ustar00The MIT License (MIT)

Copyright (c) 2014-2019 Frank de Jonge

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
flysystem-aws-s3-v3/changelog.md000064400000004537147361032630012533 0ustar00# Changelog

## 1.0.24 - 2020-02-23

* Depend on S3ClientInterface rather than the concrete client.

## 1.0.23 - 2019-06-05

* Prevent content type detection for directory creation.
* Use `rawurlencode` instead of `urlencode` to treat url encoding in a spec compliant way.

## 1.0.22 - 2019-01-31

* Invert type check where string/resource difference is determined for ContentLength option.

## 1.0.21 - 2018-10-08

* Catch multipart upload errors.

## 1.0.20 - 2018-09-25

* Fixed prefix handling for uploads (writes and updates).

## 1.0.19 - 2018-03-27

* Added ETAG to response mapping.

## 1.0.18 - 2017-06-30

### Fixed

* Allow metadata to be returned through the getMetadata method.

## 1.0.17 - 2017-06-30

### Fixed

* Allow passing options to methods that don't accept options.

## 1.0.16 - 2017-06-08

### Improved

* Allow the `Tagging` meta option.

## 1.0.15 - 2017-04-28

### Improved

* Indicate this adapter can overwrite files.

## 1.0.14 - 2017-01-02

### Improved

* Now also detect mimetypes of streams.

## 1.0.13 - 2016-06-21

### Fixed

* Uploading a remote stream no longer results in an unexpected exception.

## 1.0.12 - 2016-06-06

### Improved

* Responses are now streamed instead of downloaded fully.

## 1.0.11 - 2016-05-03

### Fixed

* [::has] A regression introduced in 1.0.10 is addressed.

## 1.0.10 - 2016-04-19

### Fixed

* [::has] The `has` method now also respects implicit directories.

## 1.0.9 - 2015-11-19

### Fixed

* [#49] Large listings only returned the last page of the listing.

## 1.0.8 - 2015-11-06

### Improved

* Non-recursive listings now retrieve a shallow listing for better performance.

## 1.0.7 - 2015-11-06

### Fixed

* The `copy` operation now `urlencode`'s the `CopySource` to allow characters like `+`.

## 1.0.6 - 2015-09-25

### Fixed

* The `has` operation now respects path prefix, bug introduced in 1.0.5.

## 1.0.5 - 2015-09-22

### Fixed

* `has` calls now use `doesObjectExist` rather than retrieving metadata.

## 1.0.4 - 2015-07-06

### Fixed

* Fixed delete return value.

## 1.0.3 - 2015-06-16

### Fixed

* Use an iterator for contents listing to break through the 1000 objects limit.

## 1.0.2 - 2015-06-06

### Fixed

* Exception due to misconfiguration no longer causes a fatal error but are properly rethrown.

## 1.0.1 - 2015-05-31

### Fixed

* Stable release depending in the first v3 release of the AWS SDK.
flysystem/deprecations.md000064400000001324147361032630011612 0ustar00# Deprecations

This document lists all the planned deprecations.

## Handlers will be removed in 2.0

The `Handler` type and associated calls will be removed in version 2.0.

### Upgrade path

You should create your own implementation for handling OOP usage,
but it's recommended to move away from using an OOP-style wrapper entirely.

The reason for this is that it's too easy for implementation details (for
your application this is Flysystem) to leak into the application. The most
important part for Flysystem is that it improves portability and creates a
solid boundary between your application core and the infrastructure you use.
The OOP-style handling breaks this principle, therefore I want to stop
promoting it. 
flysystem/src/FilesystemNotFoundException.php000064400000000327147361032630015572 0ustar00<?php

namespace League\Flysystem;

use LogicException;

/**
 * Thrown when the MountManager cannot find a filesystem.
 */
class FilesystemNotFoundException extends LogicException implements FilesystemException
{
}
flysystem/src/Adapter/Ftp.php000064400000032737147361032630012235 0ustar00<?php

namespace League\Flysystem\Adapter;

use ErrorException;
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\ConnectionErrorException;
use League\Flysystem\ConnectionRuntimeException;
use League\Flysystem\InvalidRootException;
use League\Flysystem\Util;
use League\Flysystem\Util\MimeType;

class Ftp extends AbstractFtpAdapter
{
    use StreamedCopyTrait;

    /**
     * @var int
     */
    protected $transferMode = FTP_BINARY;

    /**
     * @var null|bool
     */
    protected $ignorePassiveAddress = null;

    /**
     * @var bool
     */
    protected $recurseManually = false;

    /**
     * @var bool
     */
    protected $utf8 = false;

    /**
     * @var array
     */
    protected $configurable = [
        'host',
        'port',
        'username',
        'password',
        'ssl',
        'timeout',
        'root',
        'permPrivate',
        'permPublic',
        'passive',
        'transferMode',
        'systemType',
        'ignorePassiveAddress',
        'recurseManually',
        'utf8',
        'enableTimestampsOnUnixListings',
    ];

    /**
     * @var bool
     */
    protected $isPureFtpd;

    /**
     * Set the transfer mode.
     *
     * @param int $mode
     *
     * @return $this
     */
    public function setTransferMode($mode)
    {
        $this->transferMode = $mode;

        return $this;
    }

    /**
     * Set if Ssl is enabled.
     *
     * @param bool $ssl
     *
     * @return $this
     */
    public function setSsl($ssl)
    {
        $this->ssl = (bool) $ssl;

        return $this;
    }

    /**
     * Set if passive mode should be used.
     *
     * @param bool $passive
     */
    public function setPassive($passive = true)
    {
        $this->passive = $passive;
    }

    /**
     * @param bool $ignorePassiveAddress
     */
    public function setIgnorePassiveAddress($ignorePassiveAddress)
    {
        $this->ignorePassiveAddress = $ignorePassiveAddress;
    }

    /**
     * @param bool $recurseManually
     */
    public function setRecurseManually($recurseManually)
    {
        $this->recurseManually = $recurseManually;
    }

    /**
     * @param bool $utf8
     */
    public function setUtf8($utf8)
    {
        $this->utf8 = (bool) $utf8;
    }

    /**
     * Connect to the FTP server.
     */
    public function connect()
    {
        if ($this->ssl) {
            $this->connection = @ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout());
        } else {
            $this->connection = @ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout());
        }

        if ( ! $this->connection) {
            throw new ConnectionRuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort());
        }

        $this->login();
        $this->setUtf8Mode();
        $this->setConnectionPassiveMode();
        $this->setConnectionRoot();
        $this->isPureFtpd = $this->isPureFtpdServer();
    }

    /**
     * Set the connection to UTF-8 mode.
     */
    protected function setUtf8Mode()
    {
        if ($this->utf8) {
            $response = ftp_raw($this->connection, "OPTS UTF8 ON");
            if (substr($response[0], 0, 3) !== '200') {
                throw new ConnectionRuntimeException(
                    'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort()
                );
            }
        }
    }

    /**
     * Set the connections to passive mode.
     *
     * @throws ConnectionRuntimeException
     */
    protected function setConnectionPassiveMode()
    {
        if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) {
            ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress);
        }

        if ( ! ftp_pasv($this->connection, $this->passive)) {
            throw new ConnectionRuntimeException(
                'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort()
            );
        }
    }

    /**
     * Set the connection root.
     */
    protected function setConnectionRoot()
    {
        $root = $this->getRoot();
        $connection = $this->connection;

        if ($root && ! ftp_chdir($connection, $root)) {
            throw new InvalidRootException('Root is invalid or does not exist: ' . $this->getRoot());
        }

        // Store absolute path for further reference.
        // This is needed when creating directories and
        // initial root was a relative path, else the root
        // would be relative to the chdir'd path.
        $this->root = ftp_pwd($connection);
    }

    /**
     * Login.
     *
     * @throws ConnectionRuntimeException
     */
    protected function login()
    {
        set_error_handler(function () {
        });
        $isLoggedIn = ftp_login(
            $this->connection,
            $this->getUsername(),
            $this->getPassword()
        );
        restore_error_handler();

        if ( ! $isLoggedIn) {
            $this->disconnect();
            throw new ConnectionRuntimeException(
                'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
                ) . ', username: ' . $this->getUsername()
            );
        }
    }

    /**
     * Disconnect from the FTP server.
     */
    public function disconnect()
    {
        if (is_resource($this->connection)) {
            @ftp_close($this->connection);
        }

        $this->connection = null;
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, Config $config)
    {
        $stream = fopen('php://temp', 'w+b');
        fwrite($stream, $contents);
        rewind($stream);
        $result = $this->writeStream($path, $stream, $config);
        fclose($stream);

        if ($result === false) {
            return false;
        }

        $result['contents'] = $contents;
        $result['mimetype'] = $config->get('mimetype') ?: Util::guessMimeType($path, $contents);

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function writeStream($path, $resource, Config $config)
    {
        $this->ensureDirectory(Util::dirname($path));

        if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) {
            return false;
        }

        if ($visibility = $config->get('visibility')) {
            $this->setVisibility($path, $visibility);
        }

        $type = 'file';

        return compact('type', 'path', 'visibility');
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, Config $config)
    {
        return $this->write($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        return ftp_rename($this->getConnection(), $path, $newpath);
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        return ftp_delete($this->getConnection(), $path);
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        $connection = $this->getConnection();
        $contents = array_reverse($this->listDirectoryContents($dirname, false));

        foreach ($contents as $object) {
            if ($object['type'] === 'file') {
                if ( ! ftp_delete($connection, $object['path'])) {
                    return false;
                }
            } elseif ( ! $this->deleteDir($object['path'])) {
                return false;
            }
        }

        return ftp_rmdir($connection, $dirname);
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, Config $config)
    {
        $connection = $this->getConnection();
        $directories = explode('/', $dirname);

        foreach ($directories as $directory) {
            if (false === $this->createActualDirectory($directory, $connection)) {
                $this->setConnectionRoot();

                return false;
            }

            ftp_chdir($connection, $directory);
        }

        $this->setConnectionRoot();

        return ['type' => 'dir', 'path' => $dirname];
    }

    /**
     * Create a directory.
     *
     * @param string   $directory
     * @param resource $connection
     *
     * @return bool
     */
    protected function createActualDirectory($directory, $connection)
    {
        // List the current directory
        $listing = ftp_nlist($connection, '.') ?: [];

        foreach ($listing as $key => $item) {
            if (preg_match('~^\./.*~', $item)) {
                $listing[$key] = substr($item, 2);
            }
        }

        if (in_array($directory, $listing, true)) {
            return true;
        }

        return (boolean) ftp_mkdir($connection, $directory);
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        if ($path === '') {
            return ['type' => 'dir', 'path' => ''];
        }

        if (@ftp_chdir($this->getConnection(), $path) === true) {
            $this->setConnectionRoot();

            return ['type' => 'dir', 'path' => $path];
        }

        $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path));

        if (empty($listing) || in_array('total 0', $listing, true)) {
            return false;
        }

        if (preg_match('/.* not found/', $listing[0])) {
            return false;
        }

        if (preg_match('/^total [0-9]*$/', $listing[0])) {
            array_shift($listing);
        }

        return $this->normalizeObject($listing[0], '');
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        if ( ! $metadata = $this->getMetadata($path)) {
            return false;
        }

        $metadata['mimetype'] = MimeType::detectByFilename($path);

        return $metadata;
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        $timestamp = ftp_mdtm($this->getConnection(), $path);

        return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false;
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        if ( ! $object = $this->readStream($path)) {
            return false;
        }

        $object['contents'] = stream_get_contents($object['stream']);
        fclose($object['stream']);
        unset($object['stream']);

        return $object;
    }

    /**
     * @inheritdoc
     */
    public function readStream($path)
    {
        $stream = fopen('php://temp', 'w+b');
        $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
        rewind($stream);

        if ( ! $result) {
            fclose($stream);

            return false;
        }

        return ['type' => 'file', 'path' => $path, 'stream' => $stream];
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate();

        if ( ! ftp_chmod($this->getConnection(), $mode, $path)) {
            return false;
        }

        return compact('path', 'visibility');
    }

    /**
     * @inheritdoc
     *
     * @param string $directory
     */
    protected function listDirectoryContents($directory, $recursive = true)
    {
        $directory = str_replace('*', '\\*', $directory);

        if ($recursive && $this->recurseManually) {
            return $this->listDirectoryContentsRecursive($directory);
        }

        $options = $recursive ? '-alnR' : '-aln';
        $listing = $this->ftpRawlist($options, $directory);

        return $listing ? $this->normalizeListing($listing, $directory) : [];
    }

    /**
     * @inheritdoc
     *
     * @param string $directory
     */
    protected function listDirectoryContentsRecursive($directory)
    {
        $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory);
        $output = [];

        foreach ($listing as $item) {
            $output[] = $item;
            if ($item['type'] !== 'dir') {
                continue;
            }
            $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path']));
        }

        return $output;
    }

    /**
     * Check if the connection is open.
     *
     * @return bool
     *
     * @throws ConnectionErrorException
     */
    public function isConnected()
    {
        return is_resource($this->connection)
            && $this->getRawExecResponseCode('NOOP') === 200;
    }

    /**
     * @return bool
     */
    protected function isPureFtpdServer()
    {
        $response = ftp_raw($this->connection, 'HELP');

        return stripos(implode(' ', $response), 'Pure-FTPd') !== false;
    }

    /**
     * The ftp_rawlist function with optional escaping.
     *
     * @param string $options
     * @param string $path
     *
     * @return array
     */
    protected function ftpRawlist($options, $path)
    {
        $connection = $this->getConnection();

        if ($this->isPureFtpd) {
            $path = str_replace(' ', '\ ', $path);
        }

        return ftp_rawlist($connection, $options . ' ' . $path);
    }

    private function getRawExecResponseCode($command)
    {
        $response = @ftp_raw($this->connection, trim($command));

        return (int) preg_replace('/\D/', '', implode(' ', $response));
    }
}
flysystem/src/Adapter/CanOverwriteFiles.php000064400000000500147361032630015056 0ustar00<?php


namespace League\Flysystem\Adapter;

/**
 * Adapters that implement this interface let the Filesystem know that files can be overwritten using the write
 * functions and don't need the update function to be called. This can help improve performance when asserts are disabled.
 */
interface CanOverwriteFiles
{
}
flysystem/src/Adapter/Local.php000064400000031203147361032630012521 0ustar00<?php

namespace League\Flysystem\Adapter;

use DirectoryIterator;
use FilesystemIterator;
use finfo as Finfo;
use League\Flysystem\Config;
use League\Flysystem\Exception;
use League\Flysystem\NotSupportedException;
use League\Flysystem\UnreadableFileException;
use League\Flysystem\Util;
use LogicException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;

class Local extends AbstractAdapter
{
    /**
     * @var int
     */
    const SKIP_LINKS = 0001;

    /**
     * @var int
     */
    const DISALLOW_LINKS = 0002;

    /**
     * @var array
     */
    protected static $permissions = [
        'file' => [
            'public' => 0644,
            'private' => 0600,
        ],
        'dir' => [
            'public' => 0755,
            'private' => 0700,
        ],
    ];

    /**
     * @var string
     */
    protected $pathSeparator = DIRECTORY_SEPARATOR;

    /**
     * @var array
     */
    protected $permissionMap;

    /**
     * @var int
     */
    protected $writeFlags;

    /**
     * @var int
     */
    private $linkHandling;

    /**
     * Constructor.
     *
     * @param string $root
     * @param int    $writeFlags
     * @param int    $linkHandling
     * @param array  $permissions
     *
     * @throws LogicException
     */
    public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = [])
    {
        $root = is_link($root) ? realpath($root) : $root;
        $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);
        $this->ensureDirectory($root);

        if ( ! is_dir($root) || ! is_readable($root)) {
            throw new LogicException('The root path ' . $root . ' is not readable.');
        }

        $this->setPathPrefix($root);
        $this->writeFlags = $writeFlags;
        $this->linkHandling = $linkHandling;
    }

    /**
     * Ensure the root directory exists.
     *
     * @param string $root root directory path
     *
     * @return void
     *
     * @throws Exception in case the root directory can not be created
     */
    protected function ensureDirectory($root)
    {
        if ( ! is_dir($root)) {
            $umask = umask(0);

            if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) {
                $mkdirError = error_get_last();
            }

            umask($umask);
            clearstatcache(false, $root);

            if ( ! is_dir($root)) {
                $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
                throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage));
            }
        }
    }

    /**
     * @inheritdoc
     */
    public function has($path)
    {
        $location = $this->applyPathPrefix($path);

        return file_exists($location);
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $this->ensureDirectory(dirname($location));

        if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
            return false;
        }

        $type = 'file';
        $result = compact('contents', 'type', 'size', 'path');

        if ($visibility = $config->get('visibility')) {
            $result['visibility'] = $visibility;
            $this->setVisibility($path, $visibility);
        }

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function writeStream($path, $resource, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $this->ensureDirectory(dirname($location));
        $stream = fopen($location, 'w+b');

        if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
            return false;
        }

        $type = 'file';
        $result = compact('type', 'path');

        if ($visibility = $config->get('visibility')) {
            $this->setVisibility($path, $visibility);
            $result['visibility'] = $visibility;
        }

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function readStream($path)
    {
        $location = $this->applyPathPrefix($path);
        $stream = fopen($location, 'rb');

        return ['type' => 'file', 'path' => $path, 'stream' => $stream];
    }

    /**
     * @inheritdoc
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $size = file_put_contents($location, $contents, $this->writeFlags);

        if ($size === false) {
            return false;
        }

        $type = 'file';

        $result = compact('type', 'path', 'size', 'contents');

        if ($mimetype = $config->get('mimetype') ?: Util::guessMimeType($path, $contents)) {
            $result['mimetype'] = $mimetype;
        }

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        $location = $this->applyPathPrefix($path);
        $contents = @file_get_contents($location);

        if ($contents === false) {
            return false;
        }

        return ['type' => 'file', 'path' => $path, 'contents' => $contents];
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        $location = $this->applyPathPrefix($path);
        $destination = $this->applyPathPrefix($newpath);
        $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath));
        $this->ensureDirectory($parentDirectory);

        return rename($location, $destination);
    }

    /**
     * @inheritdoc
     */
    public function copy($path, $newpath)
    {
        $location = $this->applyPathPrefix($path);
        $destination = $this->applyPathPrefix($newpath);
        $this->ensureDirectory(dirname($destination));

        return copy($location, $destination);
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        $location = $this->applyPathPrefix($path);

        return @unlink($location);
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        $result = [];
        $location = $this->applyPathPrefix($directory);

        if ( ! is_dir($location)) {
            return [];
        }

        $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location);

        foreach ($iterator as $file) {
            $path = $this->getFilePath($file);

            if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
                continue;
            }

            $result[] = $this->normalizeFileInfo($file);
        }

        unset($iterator);

        return array_filter($result);
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        $location = $this->applyPathPrefix($path);
        clearstatcache(false, $location);
        $info = new SplFileInfo($location);

        return $this->normalizeFileInfo($info);
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        $location = $this->applyPathPrefix($path);
        $finfo = new Finfo(FILEINFO_MIME_TYPE);
        $mimetype = $finfo->file($location);

        if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty', 'application/x-empty'])) {
            $mimetype = Util\MimeType::detectByFilename($location);
        }

        return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype];
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        $location = $this->applyPathPrefix($path);
        clearstatcache(false, $location);
        $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
        $type = is_dir($location) ? 'dir' : 'file';

        foreach ($this->permissionMap[$type] as $visibility => $visibilityPermissions) {
            if ($visibilityPermissions == $permissions) {
                return compact('path', 'visibility');
            }
        }

        $visibility = substr(sprintf('%o', fileperms($location)), -4);

        return compact('path', 'visibility');
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        $location = $this->applyPathPrefix($path);
        $type = is_dir($location) ? 'dir' : 'file';
        $success = chmod($location, $this->permissionMap[$type][$visibility]);

        if ($success === false) {
            return false;
        }

        return compact('path', 'visibility');
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, Config $config)
    {
        $location = $this->applyPathPrefix($dirname);
        $umask = umask(0);
        $visibility = $config->get('visibility', 'public');
        $return = ['path' => $dirname, 'type' => 'dir'];

        if ( ! is_dir($location)) {
            if (false === @mkdir($location, $this->permissionMap['dir'][$visibility], true)
                || false === is_dir($location)) {
                $return = false;
            }
        }

        umask($umask);

        return $return;
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        $location = $this->applyPathPrefix($dirname);

        if ( ! is_dir($location)) {
            return false;
        }

        $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST);

        /** @var SplFileInfo $file */
        foreach ($contents as $file) {
            $this->guardAgainstUnreadableFileInfo($file);
            $this->deleteFileInfoObject($file);
        }

        unset($contents);

        return rmdir($location);
    }

    /**
     * @param SplFileInfo $file
     */
    protected function deleteFileInfoObject(SplFileInfo $file)
    {
        switch ($file->getType()) {
            case 'dir':
                rmdir($file->getRealPath());
                break;
            case 'link':
                unlink($file->getPathname());
                break;
            default:
                unlink($file->getRealPath());
        }
    }

    /**
     * Normalize the file info.
     *
     * @param SplFileInfo $file
     *
     * @return array|void
     *
     * @throws NotSupportedException
     */
    protected function normalizeFileInfo(SplFileInfo $file)
    {
        if ( ! $file->isLink()) {
            return $this->mapFileInfo($file);
        }

        if ($this->linkHandling & self::DISALLOW_LINKS) {
            throw NotSupportedException::forLink($file);
        }
    }

    /**
     * Get the normalized path from a SplFileInfo object.
     *
     * @param SplFileInfo $file
     *
     * @return string
     */
    protected function getFilePath(SplFileInfo $file)
    {
        $location = $file->getPathname();
        $path = $this->removePathPrefix($location);

        return trim(str_replace('\\', '/', $path), '/');
    }

    /**
     * @param string $path
     * @param int    $mode
     *
     * @return RecursiveIteratorIterator
     */
    protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST)
    {
        return new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
            $mode
        );
    }

    /**
     * @param string $path
     *
     * @return DirectoryIterator
     */
    protected function getDirectoryIterator($path)
    {
        $iterator = new DirectoryIterator($path);

        return $iterator;
    }

    /**
     * @param SplFileInfo $file
     *
     * @return array
     */
    protected function mapFileInfo(SplFileInfo $file)
    {
        $normalized = [
            'type' => $file->getType(),
            'path' => $this->getFilePath($file),
        ];

        $normalized['timestamp'] = $file->getMTime();

        if ($normalized['type'] === 'file') {
            $normalized['size'] = $file->getSize();
        }

        return $normalized;
    }

    /**
     * @param SplFileInfo $file
     *
     * @throws UnreadableFileException
     */
    protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
    {
        if ( ! $file->isReadable()) {
            throw UnreadableFileException::forFileInfo($file);
        }
    }
}
flysystem/src/Adapter/NullAdapter.php000064400000004433147361032630013707 0ustar00<?php

namespace League\Flysystem\Adapter;

use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\Adapter\Polyfill\StreamedTrait;
use League\Flysystem\Config;

class NullAdapter extends AbstractAdapter
{
    use StreamedTrait;
    use StreamedCopyTrait;

    /**
     * Check whether a file is present.
     *
     * @param string $path
     *
     * @return bool
     */
    public function has($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, Config $config)
    {
        $type = 'file';
        $result = compact('contents', 'type', 'path');

        if ($visibility = $config->get('visibility')) {
            $result['visibility'] = $visibility;
        }

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, Config $config)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        return [];
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        return compact('visibility');
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, Config $config)
    {
        return ['path' => $dirname, 'type' => 'dir'];
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        return false;
    }
}
flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php000064400000002045147361032630016666 0ustar00<?php

namespace League\Flysystem\Adapter\Polyfill;

use League\Flysystem\Config;

trait StreamedCopyTrait
{
    /**
     * Copy a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function copy($path, $newpath)
    {
        $response = $this->readStream($path);

        if ($response === false || ! is_resource($response['stream'])) {
            return false;
        }

        $result = $this->writeStream($newpath, $response['stream'], new Config());

        if ($result !== false && is_resource($response['stream'])) {
            fclose($response['stream']);
        }

        return $result !== false;
    }

    // Required abstract method

    /**
     * @param string $path
     *
     * @return resource
     */
    abstract public function readStream($path);

    /**
     * @param string   $path
     * @param resource $resource
     * @param Config   $config
     *
     * @return resource
     */
    abstract public function writeStream($path, $resource, Config $config);
}
flysystem/src/Adapter/Polyfill/StreamedTrait.php000064400000000211147361032630016024 0ustar00<?php

namespace League\Flysystem\Adapter\Polyfill;

trait StreamedTrait
{
    use StreamedReadingTrait;
    use StreamedWritingTrait;
}
flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php000064400000001575147361032630017334 0ustar00<?php

namespace League\Flysystem\Adapter\Polyfill;

/**
 * A helper for adapters that only handle strings to provide read streams.
 */
trait StreamedReadingTrait
{
    /**
     * Reads a file as a stream.
     *
     * @param string $path
     *
     * @return array|false
     *
     * @see League\Flysystem\ReadInterface::readStream()
     */
    public function readStream($path)
    {
        if ( ! $data = $this->read($path)) {
            return false;
        }

        $stream = fopen('php://temp', 'w+b');
        fwrite($stream, $data['contents']);
        rewind($stream);
        $data['stream'] = $stream;
        unset($data['contents']);

        return $data;
    }

    /**
     * Reads a file.
     *
     * @param string $path
     *
     * @return array|false
     *
     * @see League\Flysystem\ReadInterface::read()
     */
    abstract public function read($path);
}
flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php000064400000003041147361032630017374 0ustar00<?php

namespace League\Flysystem\Adapter\Polyfill;

use League\Flysystem\Config;
use League\Flysystem\Util;

trait StreamedWritingTrait
{
    /**
     * Stream fallback delegator.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config
     * @param string   $fallback
     *
     * @return mixed fallback result
     */
    protected function stream($path, $resource, Config $config, $fallback)
    {
        Util::rewindStream($resource);
        $contents = stream_get_contents($resource);
        $fallbackCall = [$this, $fallback];

        return call_user_func($fallbackCall, $path, $contents, $config);
    }

    /**
     * Write using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config
     *
     * @return mixed false or file metadata
     */
    public function writeStream($path, $resource, Config $config)
    {
        return $this->stream($path, $resource, $config, 'write');
    }

    /**
     * Update a file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config   Config object or visibility setting
     *
     * @return mixed false of file metadata
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->stream($path, $resource, $config, 'update');
    }

    // Required abstract methods
    abstract public function write($pash, $contents, Config $config);
    abstract public function update($pash, $contents, Config $config);
}
flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php000064400000001356147361032630021176 0ustar00<?php

namespace League\Flysystem\Adapter\Polyfill;

use LogicException;

trait NotSupportingVisibilityTrait
{
    /**
     * Get the visibility of a file.
     *
     * @param string $path
     *
     * @throws LogicException
     */
    public function getVisibility($path)
    {
        throw new LogicException(get_class($this) . ' does not support visibility. Path: ' . $path);
    }

    /**
     * Set the visibility for a file.
     *
     * @param string $path
     * @param string $visibility
     *
     * @throws LogicException
     */
    public function setVisibility($path, $visibility)
    {
        throw new LogicException(get_class($this) . ' does not support visibility. Path: ' . $path . ', visibility: ' . $visibility);
    }
}
flysystem/src/Adapter/SynologyFtp.php000064400000000176147361032630013771 0ustar00<?php

namespace League\Flysystem\Adapter;

class SynologyFtp extends Ftpd
{
    // This class merely exists because of BC.
}
flysystem/src/Adapter/AbstractFtpAdapter.php000064400000035422147361032630015214 0ustar00<?php

namespace League\Flysystem\Adapter;

use DateTime;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\NotSupportedException;
use League\Flysystem\SafeStorage;
use RuntimeException;

abstract class AbstractFtpAdapter extends AbstractAdapter
{
    /**
     * @var mixed
     */
    protected $connection;

    /**
     * @var string
     */
    protected $host;

    /**
     * @var int
     */
    protected $port = 21;

    /**
     * @var bool
     */
    protected $ssl = false;

    /**
     * @var int
     */
    protected $timeout = 90;

    /**
     * @var bool
     */
    protected $passive = true;

    /**
     * @var string
     */
    protected $separator = '/';

    /**
     * @var string|null
     */
    protected $root;

    /**
     * @var int
     */
    protected $permPublic = 0744;

    /**
     * @var int
     */
    protected $permPrivate = 0700;

    /**
     * @var array
     */
    protected $configurable = [];

    /**
     * @var string
     */
    protected $systemType;

    /**
     * @var SafeStorage
     */
    protected $safeStorage;

    /**
     * True to enable timestamps for FTP servers that return unix-style listings.
     *
     * @var bool
     */
    protected $enableTimestampsOnUnixListings = false;

    /**
     * Constructor.
     *
     * @param array $config
     */
    public function __construct(array $config)
    {
        $this->safeStorage = new SafeStorage();
        $this->setConfig($config);
    }

    /**
     * Set the config.
     *
     * @param array $config
     *
     * @return $this
     */
    public function setConfig(array $config)
    {
        foreach ($this->configurable as $setting) {
            if ( ! isset($config[$setting])) {
                continue;
            }

            $method = 'set' . ucfirst($setting);

            if (method_exists($this, $method)) {
                $this->$method($config[$setting]);
            }
        }

        return $this;
    }

    /**
     * Returns the host.
     *
     * @return string
     */
    public function getHost()
    {
        return $this->host;
    }

    /**
     * Set the host.
     *
     * @param string $host
     *
     * @return $this
     */
    public function setHost($host)
    {
        $this->host = $host;

        return $this;
    }

    /**
     * Set the public permission value.
     *
     * @param int $permPublic
     *
     * @return $this
     */
    public function setPermPublic($permPublic)
    {
        $this->permPublic = $permPublic;

        return $this;
    }

    /**
     * Set the private permission value.
     *
     * @param int $permPrivate
     *
     * @return $this
     */
    public function setPermPrivate($permPrivate)
    {
        $this->permPrivate = $permPrivate;

        return $this;
    }

    /**
     * Returns the ftp port.
     *
     * @return int
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Returns the root folder to work from.
     *
     * @return string
     */
    public function getRoot()
    {
        return $this->root;
    }

    /**
     * Set the ftp port.
     *
     * @param int|string $port
     *
     * @return $this
     */
    public function setPort($port)
    {
        $this->port = (int) $port;

        return $this;
    }

    /**
     * Set the root folder to work from.
     *
     * @param string $root
     *
     * @return $this
     */
    public function setRoot($root)
    {
        $this->root = rtrim($root, '\\/') . $this->separator;

        return $this;
    }

    /**
     * Returns the ftp username.
     *
     * @return string username
     */
    public function getUsername()
    {
        $username = $this->safeStorage->retrieveSafely('username');

        return $username !== null ? $username : 'anonymous';
    }

    /**
     * Set ftp username.
     *
     * @param string $username
     *
     * @return $this
     */
    public function setUsername($username)
    {
        $this->safeStorage->storeSafely('username', $username);

        return $this;
    }

    /**
     * Returns the password.
     *
     * @return string password
     */
    public function getPassword()
    {
        return $this->safeStorage->retrieveSafely('password');
    }

    /**
     * Set the ftp password.
     *
     * @param string $password
     *
     * @return $this
     */
    public function setPassword($password)
    {
        $this->safeStorage->storeSafely('password', $password);

        return $this;
    }

    /**
     * Returns the amount of seconds before the connection will timeout.
     *
     * @return int
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Set the amount of seconds before the connection should timeout.
     *
     * @param int $timeout
     *
     * @return $this
     */
    public function setTimeout($timeout)
    {
        $this->timeout = (int) $timeout;

        return $this;
    }

    /**
     * Return the FTP system type.
     *
     * @return string
     */
    public function getSystemType()
    {
        return $this->systemType;
    }

    /**
     * Set the FTP system type (windows or unix).
     *
     * @param string $systemType
     *
     * @return $this
     */
    public function setSystemType($systemType)
    {
        $this->systemType = strtolower($systemType);

        return $this;
    }

    /**
     * True to enable timestamps for FTP servers that return unix-style listings.
     *
     * @param bool $bool
     *
     * @return $this
     */
    public function setEnableTimestampsOnUnixListings($bool = false)
    {
        $this->enableTimestampsOnUnixListings = $bool;

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        return $this->listDirectoryContents($directory, $recursive);
    }

    abstract protected function listDirectoryContents($directory, $recursive = false);

    /**
     * Normalize a directory listing.
     *
     * @param array  $listing
     * @param string $prefix
     *
     * @return array directory listing
     */
    protected function normalizeListing(array $listing, $prefix = '')
    {
        $base = $prefix;
        $result = [];
        $listing = $this->removeDotDirectories($listing);

        while ($item = array_shift($listing)) {
            if (preg_match('#^.*:$#', $item)) {
                $base = preg_replace('~^\./*|:$~', '', $item);
                continue;
            }

            $result[] = $this->normalizeObject($item, $base);
        }

        return $this->sortListing($result);
    }

    /**
     * Sort a directory listing.
     *
     * @param array $result
     *
     * @return array sorted listing
     */
    protected function sortListing(array $result)
    {
        $compare = function ($one, $two) {
            return strnatcmp($one['path'], $two['path']);
        };

        usort($result, $compare);

        return $result;
    }

    /**
     * Normalize a file entry.
     *
     * @param string $item
     * @param string $base
     *
     * @return array normalized file array
     *
     * @throws NotSupportedException
     */
    protected function normalizeObject($item, $base)
    {
        $systemType = $this->systemType ?: $this->detectSystemType($item);

        if ($systemType === 'unix') {
            return $this->normalizeUnixObject($item, $base);
        } elseif ($systemType === 'windows') {
            return $this->normalizeWindowsObject($item, $base);
        }

        throw NotSupportedException::forFtpSystemType($systemType);
    }

    /**
     * Normalize a Unix file entry.
     *
     * Given $item contains:
     *    '-rw-r--r--   1 ftp      ftp           409 Aug 19 09:01 file1.txt'
     *
     * This function will return:
     * [
     *   'type' => 'file',
     *   'path' => 'file1.txt',
     *   'visibility' => 'public',
     *   'size' => 409,
     *   'timestamp' => 1566205260
     * ]
     *
     * @param string $item
     * @param string $base
     *
     * @return array normalized file array
     */
    protected function normalizeUnixObject($item, $base)
    {
        $item = preg_replace('#\s+#', ' ', trim($item), 7);

        if (count(explode(' ', $item, 9)) !== 9) {
            throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
        }

        list($permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name) = explode(' ', $item, 9);
        $type = $this->detectType($permissions);
        $path = $base === '' ? $name : $base . $this->separator . $name;

        if ($type === 'dir') {
            return compact('type', 'path');
        }

        $permissions = $this->normalizePermissions($permissions);
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
        $size = (int) $size;

        $result = compact('type', 'path', 'visibility', 'size');
        if ($this->enableTimestampsOnUnixListings) {
            $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear);
            $result += compact('timestamp');
        }

        return $result;
    }

    /**
     * Only accurate to the minute (current year), or to the day.
     *
     * Inadequacies in timestamp accuracy are due to limitations of the FTP 'LIST' command
     *
     * Note: The 'MLSD' command is a machine-readable replacement for 'LIST'
     * but many FTP servers do not support it :(
     *
     * @param string $month      e.g. 'Aug'
     * @param string $day        e.g. '19'
     * @param string $timeOrYear e.g. '09:01' OR '2015'
     *
     * @return int
     */
    protected function normalizeUnixTimestamp($month, $day, $timeOrYear)
    {
        if (is_numeric($timeOrYear)) {
            $year = $timeOrYear;
            $hour = '00';
            $minute = '00';
            $seconds = '00';
        } else {
            $year = date('Y');
            list($hour, $minute) = explode(':', $timeOrYear);
            $seconds = '00';
        }
        $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}");

        return $dateTime->getTimestamp();
    }

    /**
     * Normalize a Windows/DOS file entry.
     *
     * @param string $item
     * @param string $base
     *
     * @return array normalized file array
     */
    protected function normalizeWindowsObject($item, $base)
    {
        $item = preg_replace('#\s+#', ' ', trim($item), 3);

        if (count(explode(' ', $item, 4)) !== 4) {
            throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
        }

        list($date, $time, $size, $name) = explode(' ', $item, 4);
        $path = $base === '' ? $name : $base . $this->separator . $name;

        // Check for the correct date/time format
        $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
        $dt = DateTime::createFromFormat($format, $date . $time);
        $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time");

        if ($size === '<DIR>') {
            $type = 'dir';

            return compact('type', 'path', 'timestamp');
        }

        $type = 'file';
        $visibility = AdapterInterface::VISIBILITY_PUBLIC;
        $size = (int) $size;

        return compact('type', 'path', 'visibility', 'size', 'timestamp');
    }

    /**
     * Get the system type from a listing item.
     *
     * @param string $item
     *
     * @return string the system type
     */
    protected function detectSystemType($item)
    {
        return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix';
    }

    /**
     * Get the file type from the permissions.
     *
     * @param string $permissions
     *
     * @return string file type
     */
    protected function detectType($permissions)
    {
        return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
    }

    /**
     * Normalize a permissions string.
     *
     * @param string $permissions
     *
     * @return int
     */
    protected function normalizePermissions($permissions)
    {
        if (is_numeric($permissions)) {
            return ((int) $permissions) & 0777;
        }

        // remove the type identifier
        $permissions = substr($permissions, 1);

        // map the string rights to the numeric counterparts
        $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
        $permissions = strtr($permissions, $map);

        // split up the permission groups
        $parts = str_split($permissions, 3);

        // convert the groups
        $mapper = function ($part) {
            return array_sum(str_split($part));
        };

        // converts to decimal number
        return octdec(implode('', array_map($mapper, $parts)));
    }

    /**
     * Filter out dot-directories.
     *
     * @param array $list
     *
     * @return array
     */
    public function removeDotDirectories(array $list)
    {
        $filter = function ($line) {
            return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line);
        };

        return array_filter($list, $filter);
    }

    /**
     * @inheritdoc
     */
    public function has($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Ensure a directory exists.
     *
     * @param string $dirname
     */
    public function ensureDirectory($dirname)
    {
        $dirname = (string) $dirname;

        if ($dirname !== '' && ! $this->has($dirname)) {
            $this->createDir($dirname, new Config());
        }
    }

    /**
     * @return mixed
     */
    public function getConnection()
    {
        $tries = 0;

        while ( ! $this->isConnected() && $tries < 3) {
            $tries++;
            $this->disconnect();
            $this->connect();
        }

        return $this->connection;
    }

    /**
     * Get the public permission value.
     *
     * @return int
     */
    public function getPermPublic()
    {
        return $this->permPublic;
    }

    /**
     * Get the private permission value.
     *
     * @return int
     */
    public function getPermPrivate()
    {
        return $this->permPrivate;
    }

    /**
     * Disconnect on destruction.
     */
    public function __destruct()
    {
        $this->disconnect();
    }

    /**
     * Establish a connection.
     */
    abstract public function connect();

    /**
     * Close the connection.
     */
    abstract public function disconnect();

    /**
     * Check if a connection is active.
     *
     * @return bool
     */
    abstract public function isConnected();
}
flysystem/src/Adapter/AbstractAdapter.php000064400000002517147361032630014541 0ustar00<?php

namespace League\Flysystem\Adapter;

use League\Flysystem\AdapterInterface;

abstract class AbstractAdapter implements AdapterInterface
{
    /**
     * @var string|null path prefix
     */
    protected $pathPrefix;

    /**
     * @var string
     */
    protected $pathSeparator = '/';

    /**
     * Set the path prefix.
     *
     * @param string $prefix
     *
     * @return void
     */
    public function setPathPrefix($prefix)
    {
        $prefix = (string) $prefix;

        if ($prefix === '') {
            $this->pathPrefix = null;

            return;
        }

        $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator;
    }

    /**
     * Get the path prefix.
     *
     * @return string|null path prefix or null if pathPrefix is empty
     */
    public function getPathPrefix()
    {
        return $this->pathPrefix;
    }

    /**
     * Prefix a path.
     *
     * @param string $path
     *
     * @return string prefixed path
     */
    public function applyPathPrefix($path)
    {
        return $this->getPathPrefix() . ltrim($path, '\\/');
    }

    /**
     * Remove a path prefix.
     *
     * @param string $path
     *
     * @return string path without the prefix
     */
    public function removePathPrefix($path)
    {
        return substr($path, strlen($this->getPathPrefix()));
    }
}
flysystem/src/Adapter/Ftpd.php000064400000002116147361032630012365 0ustar00<?php

namespace League\Flysystem\Adapter;

class Ftpd extends Ftp
{
    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        if ($path === '') {
            return ['type' => 'dir', 'path' => ''];
        }
        if (@ftp_chdir($this->getConnection(), $path) === true) {
            $this->setConnectionRoot();

            return ['type' => 'dir', 'path' => $path];
        }

        if ( ! ($object = ftp_raw($this->getConnection(), 'STAT ' . $path)) || count($object) < 3) {
            return false;
        }

        if (substr($object[1], 0, 5) === "ftpd:") {
            return false;
        }

        return $this->normalizeObject($object[1], '');
    }

    /**
     * @inheritdoc
     */
    protected function listDirectoryContents($directory, $recursive = true)
    {
        $listing = ftp_rawlist($this->getConnection(), $directory, $recursive);

        if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) {
            return [];
        }

        return $this->normalizeListing($listing, $directory);
    }
}
flysystem/src/MountManager.php000064400000042011147361032630012503 0ustar00<?php

namespace League\Flysystem;

use InvalidArgumentException;
use League\Flysystem\Plugin\PluggableTrait;
use League\Flysystem\Plugin\PluginNotFoundException;

/**
 * Class MountManager.
 *
 * Proxies methods to Filesystem (@see __call):
 *
 * @method AdapterInterface getAdapter($prefix)
 * @method Config getConfig($prefix)
 * @method array listFiles($directory = '', $recursive = false)
 * @method array listPaths($directory = '', $recursive = false)
 * @method array getWithMetadata($path, array $metadata)
 * @method Filesystem flushCache()
 * @method void assertPresent($path)
 * @method void assertAbsent($path)
 * @method Filesystem addPlugin(PluginInterface $plugin)
 */
class MountManager implements FilesystemInterface
{
    use PluggableTrait;

    /**
     * @var FilesystemInterface[]
     */
    protected $filesystems = [];

    /**
     * Constructor.
     *
     * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,]
     *
     * @throws InvalidArgumentException
     */
    public function __construct(array $filesystems = [])
    {
        $this->mountFilesystems($filesystems);
    }

    /**
     * Mount filesystems.
     *
     * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,]
     *
     * @throws InvalidArgumentException
     *
     * @return $this
     */
    public function mountFilesystems(array $filesystems)
    {
        foreach ($filesystems as $prefix => $filesystem) {
            $this->mountFilesystem($prefix, $filesystem);
        }

        return $this;
    }

    /**
     * Mount filesystems.
     *
     * @param string              $prefix
     * @param FilesystemInterface $filesystem
     *
     * @throws InvalidArgumentException
     *
     * @return $this
     */
    public function mountFilesystem($prefix, FilesystemInterface $filesystem)
    {
        if ( ! is_string($prefix)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.');
        }

        $this->filesystems[$prefix] = $filesystem;

        return $this;
    }

    /**
     * Get the filesystem with the corresponding prefix.
     *
     * @param string $prefix
     *
     * @throws FilesystemNotFoundException
     *
     * @return FilesystemInterface
     */
    public function getFilesystem($prefix)
    {
        if ( ! isset($this->filesystems[$prefix])) {
            throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix);
        }

        return $this->filesystems[$prefix];
    }

    /**
     * Retrieve the prefix from an arguments array.
     *
     * @param array $arguments
     *
     * @throws InvalidArgumentException
     *
     * @return array [:prefix, :arguments]
     */
    public function filterPrefix(array $arguments)
    {
        if (empty($arguments)) {
            throw new InvalidArgumentException('At least one argument needed');
        }

        $path = array_shift($arguments);

        if ( ! is_string($path)) {
            throw new InvalidArgumentException('First argument should be a string');
        }

        list($prefix, $path) = $this->getPrefixAndPath($path);
        array_unshift($arguments, $path);

        return [$prefix, $arguments];
    }

    /**
     * @param string $directory
     * @param bool   $recursive
     *
     * @throws InvalidArgumentException
     * @throws FilesystemNotFoundException
     *
     * @return array
     */
    public function listContents($directory = '', $recursive = false)
    {
        list($prefix, $directory) = $this->getPrefixAndPath($directory);
        $filesystem = $this->getFilesystem($prefix);
        $result = $filesystem->listContents($directory, $recursive);

        foreach ($result as &$file) {
            $file['filesystem'] = $prefix;
        }

        return $result;
    }

    /**
     * Call forwarder.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @throws InvalidArgumentException
     * @throws FilesystemNotFoundException
     *
     * @return mixed
     */
    public function __call($method, $arguments)
    {
        list($prefix, $arguments) = $this->filterPrefix($arguments);

        return $this->invokePluginOnFilesystem($method, $arguments, $prefix);
    }

    /**
     * @param string $from
     * @param string $to
     * @param array  $config
     *
     * @throws InvalidArgumentException
     * @throws FilesystemNotFoundException
     * @throws FileExistsException
     *
     * @return bool
     */
    public function copy($from, $to, array $config = [])
    {
        list($prefixFrom, $from) = $this->getPrefixAndPath($from);

        $buffer = $this->getFilesystem($prefixFrom)->readStream($from);

        if ($buffer === false) {
            return false;
        }

        list($prefixTo, $to) = $this->getPrefixAndPath($to);

        $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config);

        if (is_resource($buffer)) {
            fclose($buffer);
        }

        return $result;
    }

    /**
     * List with plugin adapter.
     *
     * @param array  $keys
     * @param string $directory
     * @param bool   $recursive
     *
     * @throws InvalidArgumentException
     * @throws FilesystemNotFoundException
     *
     * @return array
     */
    public function listWith(array $keys = [], $directory = '', $recursive = false)
    {
        list($prefix, $directory) = $this->getPrefixAndPath($directory);
        $arguments = [$keys, $directory, $recursive];

        return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix);
    }

    /**
     * Move a file.
     *
     * @param string $from
     * @param string $to
     * @param array  $config
     *
     * @throws InvalidArgumentException
     * @throws FilesystemNotFoundException
     *
     * @return bool
     */
    public function move($from, $to, array $config = [])
    {
        list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from);
        list($prefixTo, $pathTo) = $this->getPrefixAndPath($to);

        if ($prefixFrom === $prefixTo) {
            $filesystem = $this->getFilesystem($prefixFrom);
            $renamed = $filesystem->rename($pathFrom, $pathTo);

            if ($renamed && isset($config['visibility'])) {
                return $filesystem->setVisibility($pathTo, $config['visibility']);
            }

            return $renamed;
        }

        $copied = $this->copy($from, $to, $config);

        if ($copied) {
            return $this->delete($from);
        }

        return false;
    }

    /**
     * Invoke a plugin on a filesystem mounted on a given prefix.
     *
     * @param string $method
     * @param array  $arguments
     * @param string $prefix
     *
     * @throws FilesystemNotFoundException
     *
     * @return mixed
     */
    public function invokePluginOnFilesystem($method, $arguments, $prefix)
    {
        $filesystem = $this->getFilesystem($prefix);

        try {
            return $this->invokePlugin($method, $arguments, $filesystem);
        } catch (PluginNotFoundException $e) {
            // Let it pass, it's ok, don't panic.
        }

        $callback = [$filesystem, $method];

        return call_user_func_array($callback, $arguments);
    }

    /**
     * @param string $path
     *
     * @throws InvalidArgumentException
     *
     * @return string[] [:prefix, :path]
     */
    protected function getPrefixAndPath($path)
    {
        if (strpos($path, '://') < 1) {
            throw new InvalidArgumentException('No prefix detected in path: ' . $path);
        }

        return explode('://', $path, 2);
    }

    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return bool
     */
    public function has($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->has($path);
    }

    /**
     * Read a file.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file contents or false on failure.
     */
    public function read($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->read($path);
    }

    /**
     * Retrieves a read-stream for a path.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return resource|false The path resource or false on failure.
     */
    public function readStream($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->readStream($path);
    }

    /**
     * Get a file's metadata.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return array|false The file metadata or false on failure.
     */
    public function getMetadata($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->getMetadata($path);
    }

    /**
     * Get a file's size.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return int|false The file size or false on failure.
     */
    public function getSize($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->getSize($path);
    }

    /**
     * Get a file's mime-type.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file mime-type or false on failure.
     */
    public function getMimetype($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->getMimetype($path);
    }

    /**
     * Get a file's timestamp.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The timestamp or false on failure.
     */
    public function getTimestamp($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->getTimestamp($path);
    }

    /**
     * Get a file's visibility.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The visibility (public|private) or false on failure.
     */
    public function getVisibility($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->getVisibility($path);
    }

    /**
     * Write a new file.
     *
     * @param string $path     The path of the new file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @throws FileExistsException
     *
     * @return bool True on success, false on failure.
     */
    public function write($path, $contents, array $config = [])
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->write($path, $contents, $config);
    }

    /**
     * Write a new file using a stream.
     *
     * @param string   $path     The path of the new file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException If $resource is not a file handle.
     * @throws FileExistsException
     *
     * @return bool True on success, false on failure.
     */
    public function writeStream($path, $resource, array $config = [])
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->writeStream($path, $resource, $config);
    }

    /**
     * Update an existing file.
     *
     * @param string $path     The path of the existing file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function update($path, $contents, array $config = [])
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->update($path, $contents, $config);
    }

    /**
     * Update an existing file using a stream.
     *
     * @param string   $path     The path of the existing file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException If $resource is not a file handle.
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function updateStream($path, $resource, array $config = [])
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->updateStream($path, $resource, $config);
    }

    /**
     * Rename a file.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileExistsException   Thrown if $newpath exists.
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function rename($path, $newpath)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->rename($path, $newpath);
    }

    /**
     * Delete a file.
     *
     * @param string $path
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function delete($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->delete($path);
    }

    /**
     * Delete a directory.
     *
     * @param string $dirname
     *
     * @throws RootViolationException Thrown if $dirname is empty.
     *
     * @return bool True on success, false on failure.
     */
    public function deleteDir($dirname)
    {
        list($prefix, $dirname) = $this->getPrefixAndPath($dirname);

        return $this->getFilesystem($prefix)->deleteDir($dirname);
    }

    /**
     * Create a directory.
     *
     * @param string $dirname The name of the new directory.
     * @param array  $config  An optional configuration array.
     *
     * @return bool True on success, false on failure.
     */
    public function createDir($dirname, array $config = [])
    {
        list($prefix, $dirname) = $this->getPrefixAndPath($dirname);

        return $this->getFilesystem($prefix)->createDir($dirname);
    }

    /**
     * Set the visibility for a file.
     *
     * @param string $path       The path to the file.
     * @param string $visibility One of 'public' or 'private'.
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function setVisibility($path, $visibility)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->setVisibility($path, $visibility);
    }

    /**
     * Create a file or update if exists.
     *
     * @param string $path     The path to the file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @return bool True on success, false on failure.
     */
    public function put($path, $contents, array $config = [])
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->put($path, $contents, $config);
    }

    /**
     * Create a file or update if exists.
     *
     * @param string   $path     The path to the file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException Thrown if $resource is not a resource.
     *
     * @return bool True on success, false on failure.
     */
    public function putStream($path, $resource, array $config = [])
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->putStream($path, $resource, $config);
    }

    /**
     * Read and delete a file.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file contents, or false on failure.
     */
    public function readAndDelete($path)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->readAndDelete($path);
    }

    /**
     * Get a file/directory handler.
     *
     * @deprecated
     *
     * @param string  $path    The path to the file.
     * @param Handler $handler An optional existing handler to populate.
     *
     * @return Handler Either a file or directory handler.
     */
    public function get($path, Handler $handler = null)
    {
        list($prefix, $path) = $this->getPrefixAndPath($path);

        return $this->getFilesystem($prefix)->get($path);
    }
}
flysystem/src/UnreadableFileException.php000064400000000531147361032630014630 0ustar00<?php

namespace League\Flysystem;

use SplFileInfo;

class UnreadableFileException extends Exception
{
    public static function forFileInfo(SplFileInfo $fileInfo)
    {
        return new static(
            sprintf(
                'Unreadable file encountered: %s',
                $fileInfo->getRealPath()
            )
        );
    }
}
flysystem/src/FilesystemException.php000064400000000106147361032630014110 0ustar00<?php

namespace League\Flysystem;

interface FilesystemException
{
}
flysystem/src/Handler.php000064400000005037147361032630011472 0ustar00<?php

namespace League\Flysystem;

use BadMethodCallException;

/**
 * @deprecated
 */
abstract class Handler
{
    /**
     * @var string
     */
    protected $path;

    /**
     * @var FilesystemInterface
     */
    protected $filesystem;

    /**
     * Constructor.
     *
     * @param FilesystemInterface $filesystem
     * @param string              $path
     */
    public function __construct(FilesystemInterface $filesystem = null, $path = null)
    {
        $this->path = $path;
        $this->filesystem = $filesystem;
    }

    /**
     * Check whether the entree is a directory.
     *
     * @return bool
     */
    public function isDir()
    {
        return $this->getType() === 'dir';
    }

    /**
     * Check whether the entree is a file.
     *
     * @return bool
     */
    public function isFile()
    {
        return $this->getType() === 'file';
    }

    /**
     * Retrieve the entree type (file|dir).
     *
     * @return string file or dir
     */
    public function getType()
    {
        $metadata = $this->filesystem->getMetadata($this->path);

        return $metadata ? $metadata['type'] : 'dir';
    }

    /**
     * Set the Filesystem object.
     *
     * @param FilesystemInterface $filesystem
     *
     * @return $this
     */
    public function setFilesystem(FilesystemInterface $filesystem)
    {
        $this->filesystem = $filesystem;

        return $this;
    }
    
    /**
     * Retrieve the Filesystem object.
     *
     * @return FilesystemInterface
     */
    public function getFilesystem()
    {
        return $this->filesystem;
    }

    /**
     * Set the entree path.
     *
     * @param string $path
     *
     * @return $this
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Retrieve the entree path.
     *
     * @return string path
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Plugins pass-through.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($method, array $arguments)
    {
        array_unshift($arguments, $this->path);
        $callback = [$this->filesystem, $method];

        try {
            return call_user_func_array($callback, $arguments);
        } catch (BadMethodCallException $e) {
            throw new BadMethodCallException(
                'Call to undefined method '
                . get_called_class()
                . '::' . $method
            );
        }
    }
}
flysystem/src/NotSupportedException.php000064400000001444147361032630014440 0ustar00<?php

namespace League\Flysystem;

use RuntimeException;
use SplFileInfo;

class NotSupportedException extends RuntimeException implements FilesystemException
{
    /**
     * Create a new exception for a link.
     *
     * @param SplFileInfo $file
     *
     * @return static
     */
    public static function forLink(SplFileInfo $file)
    {
        $message = 'Links are not supported, encountered link at ';

        return new static($message . $file->getPathname());
    }

    /**
     * Create a new exception for a link.
     *
     * @param string $systemType
     *
     * @return static
     */
    public static function forFtpSystemType($systemType)
    {
        $message = "The FTP system type '$systemType' is currently not supported.";

        return new static($message);
    }
}
flysystem/src/ConnectionRuntimeException.php000064400000000230147361032630015425 0ustar00<?php

namespace League\Flysystem;

use RuntimeException;

class ConnectionRuntimeException extends RuntimeException implements FilesystemException
{
}
flysystem/src/SafeStorage.php000064400000001350147361032630012312 0ustar00<?php

namespace League\Flysystem;

final class SafeStorage
{
    /**
     * @var string
     */
    private $hash;

    /**
     * @var array
     */
    protected static $safeStorage = [];

    public function __construct()
    {
        $this->hash = spl_object_hash($this);
        static::$safeStorage[$this->hash] = [];
    }

    public function storeSafely($key, $value)
    {
        static::$safeStorage[$this->hash][$key] = $value;
    }

    public function retrieveSafely($key)
    {
        if (array_key_exists($key, static::$safeStorage[$this->hash])) {
            return static::$safeStorage[$this->hash][$key];
        }
    }

    public function __destruct()
    {
        unset(static::$safeStorage[$this->hash]);
    }
}
flysystem/src/FileExistsException.php000064400000001273147361032630014051 0ustar00<?php

namespace League\Flysystem;

use Exception as BaseException;

class FileExistsException extends Exception
{
    /**
     * @var string
     */
    protected $path;

    /**
     * Constructor.
     *
     * @param string        $path
     * @param int           $code
     * @param BaseException $previous
     */
    public function __construct($path, $code = 0, BaseException $previous = null)
    {
        $this->path = $path;

        parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous);
    }

    /**
     * Get the path which was found.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
flysystem/src/Exception.php000064400000000161147361032630012044 0ustar00<?php

namespace League\Flysystem;

class Exception extends \Exception implements FilesystemException
{
    //
}
flysystem/src/AdapterInterface.php000064400000005040147361032630013310 0ustar00<?php

namespace League\Flysystem;

interface AdapterInterface extends ReadInterface
{
    /**
     * @const  VISIBILITY_PUBLIC  public visibility
     */
    const VISIBILITY_PUBLIC = 'public';

    /**
     * @const  VISIBILITY_PRIVATE  private visibility
     */
    const VISIBILITY_PRIVATE = 'private';

    /**
     * Write a new file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function write($path, $contents, Config $config);

    /**
     * Write a new file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function writeStream($path, $resource, Config $config);

    /**
     * Update a file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function update($path, $contents, Config $config);

    /**
     * Update a file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function updateStream($path, $resource, Config $config);

    /**
     * Rename a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function rename($path, $newpath);

    /**
     * Copy a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function copy($path, $newpath);

    /**
     * Delete a file.
     *
     * @param string $path
     *
     * @return bool
     */
    public function delete($path);

    /**
     * Delete a directory.
     *
     * @param string $dirname
     *
     * @return bool
     */
    public function deleteDir($dirname);

    /**
     * Create a directory.
     *
     * @param string $dirname directory name
     * @param Config $config
     *
     * @return array|false
     */
    public function createDir($dirname, Config $config);

    /**
     * Set the visibility for a file.
     *
     * @param string $path
     * @param string $visibility
     *
     * @return array|false file meta data
     */
    public function setVisibility($path, $visibility);
}
flysystem/src/ReadInterface.php000064400000003052147361032630012604 0ustar00<?php

namespace League\Flysystem;

interface ReadInterface
{
    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return array|bool|null
     */
    public function has($path);

    /**
     * Read a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function read($path);

    /**
     * Read a file as a stream.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function readStream($path);

    /**
     * List contents of a directory.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    public function listContents($directory = '', $recursive = false);

    /**
     * Get all the meta data of a file or directory.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getMetadata($path);

    /**
     * Get the size of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getSize($path);

    /**
     * Get the mimetype of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getMimetype($path);

    /**
     * Get the last modified time of a file as a timestamp.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getTimestamp($path);

    /**
     * Get the visibility of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getVisibility($path);
}
flysystem/src/Util/ContentListingFormatter.php000064400000005034147361032630015657 0ustar00<?php

namespace League\Flysystem\Util;

use League\Flysystem\Util;

/**
 * @internal
 */
class ContentListingFormatter
{
    /**
     * @var string
     */
    private $directory;

    /**
     * @var bool
     */
    private $recursive;

    /**
     * @var bool
     */
    private $caseSensitive;

    /**
     * @param string $directory
     * @param bool   $recursive
     */
    public function __construct($directory, $recursive, $caseSensitive = true)
    {
        $this->directory = rtrim($directory, '/');
        $this->recursive = $recursive;
        $this->caseSensitive = $caseSensitive;
    }

    /**
     * Format contents listing.
     *
     * @param array $listing
     *
     * @return array
     */
    public function formatListing(array $listing)
    {
        $listing = array_filter(array_map([$this, 'addPathInfo'], $listing), [$this, 'isEntryOutOfScope']);

        return $this->sortListing(array_values($listing));
    }

    private function addPathInfo(array $entry)
    {
        return $entry + Util::pathinfo($entry['path']);
    }

    /**
     * Determine if the entry is out of scope.
     *
     * @param array $entry
     *
     * @return bool
     */
    private function isEntryOutOfScope(array $entry)
    {
        if (empty($entry['path']) && $entry['path'] !== '0') {
            return false;
        }

        if ($this->recursive) {
            return $this->residesInDirectory($entry);
        }

        return $this->isDirectChild($entry);
    }

    /**
     * Check if the entry resides within the parent directory.
     *
     * @param array $entry
     *
     * @return bool
     */
    private function residesInDirectory(array $entry)
    {
        if ($this->directory === '') {
            return true;
        }

        return $this->caseSensitive
            ? strpos($entry['path'], $this->directory . '/') === 0
            : stripos($entry['path'], $this->directory . '/') === 0;
    }

    /**
     * Check if the entry is a direct child of the directory.
     *
     * @param array $entry
     *
     * @return bool
     */
    private function isDirectChild(array $entry)
    {
        return $this->caseSensitive
            ? $entry['dirname'] === $this->directory
            : strcasecmp($this->directory, $entry['dirname']) === 0;
    }

    /**
     * @param array $listing
     *
     * @return array
     */
    private function sortListing(array $listing)
    {
        usort($listing, function ($a, $b) {
            return strcasecmp($a['path'], $b['path']);
        });

        return $listing;
    }
}
flysystem/src/Util/MimeType.php000064400000022645147361032630012567 0ustar00<?php

namespace League\Flysystem\Util;

use ErrorException;
use finfo;

/**
 * @internal
 */
class MimeType
{
    protected static $extensionToMimeTypeMap = [
        'hqx' => 'application/mac-binhex40',
        'cpt' => 'application/mac-compactpro',
        'csv' => 'text/csv',
        'bin' => 'application/octet-stream',
        'dms' => 'application/octet-stream',
        'lha' => 'application/octet-stream',
        'lzh' => 'application/octet-stream',
        'exe' => 'application/octet-stream',
        'class' => 'application/octet-stream',
        'psd' => 'application/x-photoshop',
        'so' => 'application/octet-stream',
        'sea' => 'application/octet-stream',
        'dll' => 'application/octet-stream',
        'oda' => 'application/oda',
        'pdf' => 'application/pdf',
        'ai' => 'application/pdf',
        'eps' => 'application/postscript',
        'epub' => 'application/epub+zip',
        'ps' => 'application/postscript',
        'smi' => 'application/smil',
        'smil' => 'application/smil',
        'mif' => 'application/vnd.mif',
        'xls' => 'application/vnd.ms-excel',
        'xlt' => 'application/vnd.ms-excel',
        'xla' => 'application/vnd.ms-excel',
        'ppt' => 'application/powerpoint',
        'pot' => 'application/vnd.ms-powerpoint',
        'pps' => 'application/vnd.ms-powerpoint',
        'ppa' => 'application/vnd.ms-powerpoint',
        'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
        'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
        'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
        'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
        'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
        'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
        'wbxml' => 'application/wbxml',
        'wmlc' => 'application/wmlc',
        'dcr' => 'application/x-director',
        'dir' => 'application/x-director',
        'dxr' => 'application/x-director',
        'dvi' => 'application/x-dvi',
        'gtar' => 'application/x-gtar',
        'gz' => 'application/x-gzip',
        'gzip' => 'application/x-gzip',
        'php' => 'application/x-httpd-php',
        'php4' => 'application/x-httpd-php',
        'php3' => 'application/x-httpd-php',
        'phtml' => 'application/x-httpd-php',
        'phps' => 'application/x-httpd-php-source',
        'js' => 'application/javascript',
        'swf' => 'application/x-shockwave-flash',
        'sit' => 'application/x-stuffit',
        'tar' => 'application/x-tar',
        'tgz' => 'application/x-tar',
        'z' => 'application/x-compress',
        'xhtml' => 'application/xhtml+xml',
        'xht' => 'application/xhtml+xml',
        'rdf' => 'application/rdf+xml',
        'zip' => 'application/x-zip',
        'rar' => 'application/x-rar',
        'mid' => 'audio/midi',
        'midi' => 'audio/midi',
        'mpga' => 'audio/mpeg',
        'mp2' => 'audio/mpeg',
        'mp3' => 'audio/mpeg',
        'aif' => 'audio/x-aiff',
        'aiff' => 'audio/x-aiff',
        'aifc' => 'audio/x-aiff',
        'ram' => 'audio/x-pn-realaudio',
        'rm' => 'audio/x-pn-realaudio',
        'rpm' => 'audio/x-pn-realaudio-plugin',
        'ra' => 'audio/x-realaudio',
        'rv' => 'video/vnd.rn-realvideo',
        'wav' => 'audio/x-wav',
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'jpe' => 'image/jpeg',
        'png' => 'image/png',
        'gif' => 'image/gif',
        'bmp' => 'image/bmp',
        'tiff' => 'image/tiff',
        'tif' => 'image/tiff',
        'svg' => 'image/svg+xml',
        'css' => 'text/css',
        'html' => 'text/html',
        'htm' => 'text/html',
        'shtml' => 'text/html',
        'txt' => 'text/plain',
        'text' => 'text/plain',
        'log' => 'text/plain',
        'markdown' => 'text/markdown',
        'md' => 'text/markdown',
        'rtx' => 'text/richtext',
        'rtf' => 'text/rtf',
        'xml' => 'application/xml',
        'xsl' => 'application/xml',
        'dmn' => 'application/octet-stream',
        'bpmn' => 'application/octet-stream',
        'mpeg' => 'video/mpeg',
        'mpg' => 'video/mpeg',
        'mpe' => 'video/mpeg',
        'qt' => 'video/quicktime',
        'mov' => 'video/quicktime',
        'avi' => 'video/x-msvideo',
        'movie' => 'video/x-sgi-movie',
        'doc' => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
        'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
        'dot' => 'application/msword',
        'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
        'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
        'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
        'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
        'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
        'word' => 'application/msword',
        'xl' => 'application/excel',
        'eml' => 'message/rfc822',
        'json' => 'application/json',
        'pem' => 'application/x-x509-user-cert',
        'p10' => 'application/x-pkcs10',
        'p12' => 'application/x-pkcs12',
        'p7a' => 'application/x-pkcs7-signature',
        'p7c' => 'application/pkcs7-mime',
        'p7m' => 'application/pkcs7-mime',
        'p7r' => 'application/x-pkcs7-certreqresp',
        'p7s' => 'application/pkcs7-signature',
        'crt' => 'application/x-x509-ca-cert',
        'crl' => 'application/pkix-crl',
        'der' => 'application/x-x509-ca-cert',
        'kdb' => 'application/octet-stream',
        'pgp' => 'application/pgp',
        'gpg' => 'application/gpg-keys',
        'sst' => 'application/octet-stream',
        'csr' => 'application/octet-stream',
        'rsa' => 'application/x-pkcs7',
        'cer' => 'application/pkix-cert',
        '3g2' => 'video/3gpp2',
        '3gp' => 'video/3gp',
        'mp4' => 'video/mp4',
        'm4a' => 'audio/x-m4a',
        'f4v' => 'video/mp4',
        'webm' => 'video/webm',
        'aac' => 'audio/x-acc',
        'm4u' => 'application/vnd.mpegurl',
        'm3u' => 'text/plain',
        'xspf' => 'application/xspf+xml',
        'vlc' => 'application/videolan',
        'wmv' => 'video/x-ms-wmv',
        'au' => 'audio/x-au',
        'ac3' => 'audio/ac3',
        'flac' => 'audio/x-flac',
        'ogg' => 'audio/ogg',
        'kmz' => 'application/vnd.google-earth.kmz',
        'kml' => 'application/vnd.google-earth.kml+xml',
        'ics' => 'text/calendar',
        'zsh' => 'text/x-scriptzsh',
        '7zip' => 'application/x-7z-compressed',
        'cdr' => 'application/cdr',
        'wma' => 'audio/x-ms-wma',
        'jar' => 'application/java-archive',
        'tex' => 'application/x-tex',
        'latex' => 'application/x-latex',
        'odt' => 'application/vnd.oasis.opendocument.text',
        'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
        'odp' => 'application/vnd.oasis.opendocument.presentation',
        'odg' => 'application/vnd.oasis.opendocument.graphics',
        'odc' => 'application/vnd.oasis.opendocument.chart',
        'odf' => 'application/vnd.oasis.opendocument.formula',
        'odi' => 'application/vnd.oasis.opendocument.image',
        'odm' => 'application/vnd.oasis.opendocument.text-master',
        'odb' => 'application/vnd.oasis.opendocument.database',
        'ott' => 'application/vnd.oasis.opendocument.text-template',
        'webp' => 'image/webp',
    ];

    /**
     * Detects MIME Type based on given content.
     *
     * @param mixed $content
     *
     * @return string|null MIME Type or NULL if no mime type detected
     */
    public static function detectByContent($content)
    {
        if ( ! class_exists('finfo') || ! is_string($content)) {
            return null;
        }
        try {
            $finfo = new finfo(FILEINFO_MIME_TYPE);

            return $finfo->buffer($content) ?: null;
            // @codeCoverageIgnoreStart
        } catch (ErrorException $e) {
            // This is caused by an array to string conversion error.
        }
    } // @codeCoverageIgnoreEnd

    /**
     * Detects MIME Type based on file extension.
     *
     * @param string $extension
     *
     * @return string|null MIME Type or NULL if no extension detected
     */
    public static function detectByFileExtension($extension)
    {
        return isset(static::$extensionToMimeTypeMap[$extension])
            ? static::$extensionToMimeTypeMap[$extension]
            : 'text/plain';
    }

    /**
     * @param string $filename
     *
     * @return string|null MIME Type or NULL if no extension detected
     */
    public static function detectByFilename($filename)
    {
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

        return empty($extension) ? 'text/plain' : static::detectByFileExtension($extension);
    }

    /**
     * @return array Map of file extension to MIME Type
     */
    public static function getExtensionToMimeTypeMap()
    {
        return static::$extensionToMimeTypeMap;
    }
}
flysystem/src/Util/StreamHasher.php000064400000001121147361032630013406 0ustar00<?php

namespace League\Flysystem\Util;

class StreamHasher
{
    /**
     * @var string
     */
    private $algo;

    /**
     * StreamHasher constructor.
     *
     * @param string $algo
     */
    public function __construct($algo)
    {
        $this->algo = $algo;
    }

    /**
     * @param resource $resource
     *
     * @return string
     */
    public function hash($resource)
    {
        rewind($resource);
        $context = hash_init($this->algo);
        hash_update_stream($context, $resource);
        fclose($resource);

        return hash_final($context);
    }
}
flysystem/src/InvalidRootException.php000064400000000222147361032630014215 0ustar00<?php

namespace League\Flysystem;

use RuntimeException;

class InvalidRootException extends RuntimeException implements FilesystemException
{
}
flysystem/src/Directory.php000064400000001052147361032630012052 0ustar00<?php

namespace League\Flysystem;

/**
 * @deprecated
 */
class Directory extends Handler
{
    /**
     * Delete the directory.
     *
     * @return bool
     */
    public function delete()
    {
        return $this->filesystem->deleteDir($this->path);
    }

    /**
     * List the directory contents.
     *
     * @param bool $recursive
     *
     * @return array|bool directory contents or false
     */
    public function getContents($recursive = false)
    {
        return $this->filesystem->listContents($this->path, $recursive);
    }
}
flysystem/src/RootViolationException.php000064400000000227147361032630014600 0ustar00<?php

namespace League\Flysystem;

use LogicException;

class RootViolationException extends LogicException implements FilesystemException
{
    //
}
flysystem/src/ConnectionErrorException.php000064400000000222147361032630015074 0ustar00<?php

namespace League\Flysystem;

use ErrorException;

class ConnectionErrorException extends ErrorException implements FilesystemException
{
}
flysystem/src/FilesystemInterface.php000064400000017007147361032630014062 0ustar00<?php

namespace League\Flysystem;

use InvalidArgumentException;

interface FilesystemInterface
{
    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return bool
     */
    public function has($path);

    /**
     * Read a file.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file contents or false on failure.
     */
    public function read($path);

    /**
     * Retrieves a read-stream for a path.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return resource|false The path resource or false on failure.
     */
    public function readStream($path);

    /**
     * List contents of a directory.
     *
     * @param string $directory The directory to list.
     * @param bool   $recursive Whether to list recursively.
     *
     * @return array A list of file metadata.
     */
    public function listContents($directory = '', $recursive = false);

    /**
     * Get a file's metadata.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return array|false The file metadata or false on failure.
     */
    public function getMetadata($path);

    /**
     * Get a file's size.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return int|false The file size or false on failure.
     */
    public function getSize($path);

    /**
     * Get a file's mime-type.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file mime-type or false on failure.
     */
    public function getMimetype($path);

    /**
     * Get a file's timestamp.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The timestamp or false on failure.
     */
    public function getTimestamp($path);

    /**
     * Get a file's visibility.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The visibility (public|private) or false on failure.
     */
    public function getVisibility($path);

    /**
     * Write a new file.
     *
     * @param string $path     The path of the new file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @throws FileExistsException
     *
     * @return bool True on success, false on failure.
     */
    public function write($path, $contents, array $config = []);

    /**
     * Write a new file using a stream.
     *
     * @param string   $path     The path of the new file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException If $resource is not a file handle.
     * @throws FileExistsException
     *
     * @return bool True on success, false on failure.
     */
    public function writeStream($path, $resource, array $config = []);

    /**
     * Update an existing file.
     *
     * @param string $path     The path of the existing file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function update($path, $contents, array $config = []);

    /**
     * Update an existing file using a stream.
     *
     * @param string   $path     The path of the existing file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException If $resource is not a file handle.
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function updateStream($path, $resource, array $config = []);

    /**
     * Rename a file.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileExistsException   Thrown if $newpath exists.
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function rename($path, $newpath);

    /**
     * Copy a file.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileExistsException   Thrown if $newpath exists.
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function copy($path, $newpath);

    /**
     * Delete a file.
     *
     * @param string $path
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function delete($path);

    /**
     * Delete a directory.
     *
     * @param string $dirname
     *
     * @throws RootViolationException Thrown if $dirname is empty.
     *
     * @return bool True on success, false on failure.
     */
    public function deleteDir($dirname);

    /**
     * Create a directory.
     *
     * @param string $dirname The name of the new directory.
     * @param array  $config  An optional configuration array.
     *
     * @return bool True on success, false on failure.
     */
    public function createDir($dirname, array $config = []);

    /**
     * Set the visibility for a file.
     *
     * @param string $path       The path to the file.
     * @param string $visibility One of 'public' or 'private'.
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function setVisibility($path, $visibility);

    /**
     * Create a file or update if exists.
     *
     * @param string $path     The path to the file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @return bool True on success, false on failure.
     */
    public function put($path, $contents, array $config = []);

    /**
     * Create a file or update if exists.
     *
     * @param string   $path     The path to the file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException Thrown if $resource is not a resource.
     *
     * @return bool True on success, false on failure.
     */
    public function putStream($path, $resource, array $config = []);

    /**
     * Read and delete a file.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file contents, or false on failure.
     */
    public function readAndDelete($path);

    /**
     * Get a file/directory handler.
     *
     * @deprecated
     *
     * @param string  $path    The path to the file.
     * @param Handler $handler An optional existing handler to populate.
     *
     * @return Handler Either a file or directory handler.
     */
    public function get($path, Handler $handler = null);

    /**
     * Register a plugin.
     *
     * @param PluginInterface $plugin The plugin to register.
     *
     * @return $this
     */
    public function addPlugin(PluginInterface $plugin);
}
flysystem/src/Filesystem.php000064400000024005147361032630012235 0ustar00<?php

namespace League\Flysystem;

use InvalidArgumentException;
use League\Flysystem\Adapter\CanOverwriteFiles;
use League\Flysystem\Plugin\PluggableTrait;
use League\Flysystem\Util\ContentListingFormatter;

/**
 * @method array getWithMetadata(string $path, array $metadata)
 * @method bool  forceCopy(string $path, string $newpath)
 * @method bool  forceRename(string $path, string $newpath)
 * @method array listFiles(string $path = '', boolean $recursive = false)
 * @method array listPaths(string $path = '', boolean $recursive = false)
 * @method array listWith(array $keys = [], $directory = '', $recursive = false)
 */
class Filesystem implements FilesystemInterface
{
    use PluggableTrait;
    use ConfigAwareTrait;

    /**
     * @var AdapterInterface
     */
    protected $adapter;

    /**
     * Constructor.
     *
     * @param AdapterInterface $adapter
     * @param Config|array     $config
     */
    public function __construct(AdapterInterface $adapter, $config = null)
    {
        $this->adapter = $adapter;
        $this->setConfig($config);
    }

    /**
     * Get the Adapter.
     *
     * @return AdapterInterface adapter
     */
    public function getAdapter()
    {
        return $this->adapter;
    }

    /**
     * @inheritdoc
     */
    public function has($path)
    {
        $path = Util::normalizePath($path);

        return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path);
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, array $config = [])
    {
        $path = Util::normalizePath($path);
        $this->assertAbsent($path);
        $config = $this->prepareConfig($config);

        return (bool) $this->getAdapter()->write($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function writeStream($path, $resource, array $config = [])
    {
        if ( ! is_resource($resource)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
        }

        $path = Util::normalizePath($path);
        $this->assertAbsent($path);
        $config = $this->prepareConfig($config);

        Util::rewindStream($resource);

        return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function put($path, $contents, array $config = [])
    {
        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);

        if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
            return (bool) $this->getAdapter()->update($path, $contents, $config);
        }

        return (bool) $this->getAdapter()->write($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function putStream($path, $resource, array $config = [])
    {
        if ( ! is_resource($resource)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
        }

        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);
        Util::rewindStream($resource);

        if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
            return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
        }

        return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function readAndDelete($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);
        $contents = $this->read($path);

        if ($contents === false) {
            return false;
        }

        $this->delete($path);

        return $contents;
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, array $config = [])
    {
        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);

        $this->assertPresent($path);

        return (bool) $this->getAdapter()->update($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function updateStream($path, $resource, array $config = [])
    {
        if ( ! is_resource($resource)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
        }

        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);
        $this->assertPresent($path);
        Util::rewindStream($resource);

        return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if ( ! ($object = $this->getAdapter()->read($path))) {
            return false;
        }

        return $object['contents'];
    }

    /**
     * @inheritdoc
     */
    public function readStream($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if ( ! $object = $this->getAdapter()->readStream($path)) {
            return false;
        }

        return $object['stream'];
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        $path = Util::normalizePath($path);
        $newpath = Util::normalizePath($newpath);
        $this->assertPresent($path);
        $this->assertAbsent($newpath);

        return (bool) $this->getAdapter()->rename($path, $newpath);
    }

    /**
     * @inheritdoc
     */
    public function copy($path, $newpath)
    {
        $path = Util::normalizePath($path);
        $newpath = Util::normalizePath($newpath);
        $this->assertPresent($path);
        $this->assertAbsent($newpath);

        return $this->getAdapter()->copy($path, $newpath);
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        return $this->getAdapter()->delete($path);
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        $dirname = Util::normalizePath($dirname);

        if ($dirname === '') {
            throw new RootViolationException('Root directories can not be deleted.');
        }

        return (bool) $this->getAdapter()->deleteDir($dirname);
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, array $config = [])
    {
        $dirname = Util::normalizePath($dirname);
        $config = $this->prepareConfig($config);

        return (bool) $this->getAdapter()->createDir($dirname, $config);
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        $directory = Util::normalizePath($directory);
        $contents = $this->getAdapter()->listContents($directory, $recursive);

        return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true)))
            ->formatListing($contents);
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) {
            return false;
        }

        return $object['mimetype'];
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) {
            return false;
        }

        return $object['timestamp'];
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) {
            return false;
        }

        return $object['visibility'];
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) {
            return false;
        }

        return (int) $object['size'];
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        return (bool) $this->getAdapter()->setVisibility($path, $visibility);
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        return $this->getAdapter()->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function get($path, Handler $handler = null)
    {
        $path = Util::normalizePath($path);

        if ( ! $handler) {
            $metadata = $this->getMetadata($path);
            $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path);
        }

        $handler->setPath($path);
        $handler->setFilesystem($this);

        return $handler;
    }

    /**
     * Assert a file is present.
     *
     * @param string $path path to file
     *
     * @throws FileNotFoundException
     *
     * @return void
     */
    public function assertPresent($path)
    {
        if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) {
            throw new FileNotFoundException($path);
        }
    }

    /**
     * Assert a file is absent.
     *
     * @param string $path path to file
     *
     * @throws FileExistsException
     *
     * @return void
     */
    public function assertAbsent($path)
    {
        if ($this->config->get('disable_asserts', false) === false && $this->has($path)) {
            throw new FileExistsException($path);
        }
    }
}
flysystem/src/Config.php000064400000003701147361032630011316 0ustar00<?php

namespace League\Flysystem;

class Config
{
    /**
     * @var array
     */
    protected $settings = [];

    /**
     * @var Config|null
     */
    protected $fallback;

    /**
     * Constructor.
     *
     * @param array $settings
     */
    public function __construct(array $settings = [])
    {
        $this->settings = $settings;
    }

    /**
     * Get a setting.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed config setting or default when not found
     */
    public function get($key, $default = null)
    {
        if ( ! array_key_exists($key, $this->settings)) {
            return $this->getDefault($key, $default);
        }

        return $this->settings[$key];
    }

    /**
     * Check if an item exists by key.
     *
     * @param string $key
     *
     * @return bool
     */
    public function has($key)
    {
        if (array_key_exists($key, $this->settings)) {
            return true;
        }

        return $this->fallback instanceof Config
            ? $this->fallback->has($key)
            : false;
    }

    /**
     * Try to retrieve a default setting from a config fallback.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed config setting or default when not found
     */
    protected function getDefault($key, $default)
    {
        if ( ! $this->fallback) {
            return $default;
        }

        return $this->fallback->get($key, $default);
    }

    /**
     * Set a setting.
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return $this
     */
    public function set($key, $value)
    {
        $this->settings[$key] = $value;

        return $this;
    }

    /**
     * Set the fallback.
     *
     * @param Config $fallback
     *
     * @return $this
     */
    public function setFallback(Config $fallback)
    {
        $this->fallback = $fallback;

        return $this;
    }
}
flysystem/src/ConfigAwareTrait.php000064400000001523147361032630013302 0ustar00<?php

namespace League\Flysystem;

/**
 * @internal
 */
trait ConfigAwareTrait
{
    /**
     * @var Config
     */
    protected $config;

    /**
     * Set the config.
     *
     * @param Config|array|null $config
     */
    protected function setConfig($config)
    {
        $this->config = $config ? Util::ensureConfig($config) : new Config;
    }

    /**
     * Get the Config.
     *
     * @return Config config object
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Convert a config array to a Config object with the correct fallback.
     *
     * @param array $config
     *
     * @return Config
     */
    protected function prepareConfig(array $config)
    {
        $config = new Config($config);
        $config->setFallback($this->getConfig());

        return $config;
    }
}
flysystem/src/Plugin/ListWith.php000064400000002651147361032630013121 0ustar00<?php

namespace League\Flysystem\Plugin;

class ListWith extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'listWith';
    }

    /**
     * List contents with metadata.
     *
     * @param array  $keys
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array listing with metadata
     */
    public function handle(array $keys = [], $directory = '', $recursive = false)
    {
        $contents = $this->filesystem->listContents($directory, $recursive);

        foreach ($contents as $index => $object) {
            if ($object['type'] === 'file') {
                $missingKeys = array_diff($keys, array_keys($object));
                $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object);
            }
        }

        return $contents;
    }

    /**
     * Get a meta-data value by key name.
     *
     * @param array  $object
     * @param string $key
     *
     * @return array
     */
    protected function getMetadataByName(array $object, $key)
    {
        $method = 'get' . ucfirst($key);

        if ( ! method_exists($this->filesystem, $method)) {
            throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key);
        }

        $object[$key] = $this->filesystem->{$method}($object['path']);

        return $object;
    }
}
flysystem/src/Plugin/PluginNotFoundException.php000064400000000267147361032630016145 0ustar00<?php

namespace League\Flysystem\Plugin;

use LogicException;

class PluginNotFoundException extends LogicException
{
    // This exception doesn't require additional information.
}
flysystem/src/Plugin/GetWithMetadata.php000064400000002243147361032630014363 0ustar00<?php

namespace League\Flysystem\Plugin;

use InvalidArgumentException;
use League\Flysystem\FileNotFoundException;

class GetWithMetadata extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'getWithMetadata';
    }

    /**
     * Get metadata for an object with required metadata.
     *
     * @param string $path     path to file
     * @param array  $metadata metadata keys
     *
     * @throws InvalidArgumentException
     * @throws FileNotFoundException
     *
     * @return array|false metadata
     */
    public function handle($path, array $metadata)
    {
        $object = $this->filesystem->getMetadata($path);

        if ( ! $object) {
            return false;
        }

        $keys = array_diff($metadata, array_keys($object));

        foreach ($keys as $key) {
            if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) {
                throw new InvalidArgumentException('Could not fetch metadata: ' . $key);
            }

            $object[$key] = $this->filesystem->{$method}($path);
        }

        return $object;
    }
}
flysystem/src/Plugin/ListFiles.php000064400000001276147361032630013252 0ustar00<?php

namespace League\Flysystem\Plugin;

class ListFiles extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'listFiles';
    }

    /**
     * List all files in the directory.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    public function handle($directory = '', $recursive = false)
    {
        $contents = $this->filesystem->listContents($directory, $recursive);

        $filter = function ($object) {
            return $object['type'] === 'file';
        };

        return array_values(array_filter($contents, $filter));
    }
}
flysystem/src/Plugin/EmptyDir.php000064400000001254147361032630013105 0ustar00<?php

namespace League\Flysystem\Plugin;

class EmptyDir extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'emptyDir';
    }

    /**
     * Empty a directory's contents.
     *
     * @param string $dirname
     */
    public function handle($dirname)
    {
        $listing = $this->filesystem->listContents($dirname, false);

        foreach ($listing as $item) {
            if ($item['type'] === 'dir') {
                $this->filesystem->deleteDir($item['path']);
            } else {
                $this->filesystem->delete($item['path']);
            }
        }
    }
}
flysystem/src/Plugin/AbstractPlugin.php000064400000000744147361032630014275 0ustar00<?php

namespace League\Flysystem\Plugin;

use League\Flysystem\FilesystemInterface;
use League\Flysystem\PluginInterface;

abstract class AbstractPlugin implements PluginInterface
{
    /**
     * @var FilesystemInterface
     */
    protected $filesystem;

    /**
     * Set the Filesystem object.
     *
     * @param FilesystemInterface $filesystem
     */
    public function setFilesystem(FilesystemInterface $filesystem)
    {
        $this->filesystem = $filesystem;
    }
}
flysystem/src/Plugin/PluggableTrait.php000064400000004330147361032630014254 0ustar00<?php

namespace League\Flysystem\Plugin;

use BadMethodCallException;
use League\Flysystem\FilesystemInterface;
use League\Flysystem\PluginInterface;
use LogicException;

trait PluggableTrait
{
    /**
     * @var array
     */
    protected $plugins = [];

    /**
     * Register a plugin.
     *
     * @param PluginInterface $plugin
     *
     * @throws LogicException
     *
     * @return $this
     */
    public function addPlugin(PluginInterface $plugin)
    {
        if ( ! method_exists($plugin, 'handle')) {
            throw new LogicException(get_class($plugin) . ' does not have a handle method.');
        }

        $this->plugins[$plugin->getMethod()] = $plugin;

        return $this;
    }

    /**
     * Find a specific plugin.
     *
     * @param string $method
     *
     * @throws PluginNotFoundException
     *
     * @return PluginInterface
     */
    protected function findPlugin($method)
    {
        if ( ! isset($this->plugins[$method])) {
            throw new PluginNotFoundException('Plugin not found for method: ' . $method);
        }

        return $this->plugins[$method];
    }

    /**
     * Invoke a plugin by method name.
     *
     * @param string              $method
     * @param array               $arguments
     * @param FilesystemInterface $filesystem
     *
     * @throws PluginNotFoundException
     *
     * @return mixed
     */
    protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem)
    {
        $plugin = $this->findPlugin($method);
        $plugin->setFilesystem($filesystem);
        $callback = [$plugin, 'handle'];

        return call_user_func_array($callback, $arguments);
    }

    /**
     * Plugins pass-through.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @throws BadMethodCallException
     *
     * @return mixed
     */
    public function __call($method, array $arguments)
    {
        try {
            return $this->invokePlugin($method, $arguments, $this);
        } catch (PluginNotFoundException $e) {
            throw new BadMethodCallException(
                'Call to undefined method '
                . get_class($this)
                . '::' . $method
            );
        }
    }
}
flysystem/src/Plugin/ListPaths.php000064400000001235147361032630013262 0ustar00<?php

namespace League\Flysystem\Plugin;

class ListPaths extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'listPaths';
    }

    /**
     * List all paths.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array paths
     */
    public function handle($directory = '', $recursive = false)
    {
        $result = [];
        $contents = $this->filesystem->listContents($directory, $recursive);

        foreach ($contents as $object) {
            $result[] = $object['path'];
        }

        return $result;
    }
}
flysystem/src/Plugin/ForcedRename.php000064400000002042147361032630013676 0ustar00<?php

namespace League\Flysystem\Plugin;

use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;

class ForcedRename extends AbstractPlugin
{
    /**
     * @inheritdoc
     */
    public function getMethod()
    {
        return 'forceRename';
    }

    /**
     * Renames a file, overwriting the destination if it exists.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileNotFoundException Thrown if $path does not exist.
     * @throws FileExistsException
     *
     * @return bool True on success, false on failure.
     */
    public function handle($path, $newpath)
    {
        try {
            $deleted = $this->filesystem->delete($newpath);
        } catch (FileNotFoundException $e) {
            // The destination path does not exist. That's ok.
            $deleted = true;
        }

        if ($deleted) {
            return $this->filesystem->rename($path, $newpath);
        }

        return false;
    }
}
flysystem/src/Plugin/ForcedCopy.php000064400000002021147361032630013376 0ustar00<?php

namespace League\Flysystem\Plugin;

use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;

class ForcedCopy extends AbstractPlugin
{
    /**
     * @inheritdoc
     */
    public function getMethod()
    {
        return 'forceCopy';
    }

    /**
     * Copies a file, overwriting any existing files.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileExistsException
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function handle($path, $newpath)
    {
        try {
            $deleted = $this->filesystem->delete($newpath);
        } catch (FileNotFoundException $e) {
            // The destination path does not exist. That's ok.
            $deleted = true;
        }

        if ($deleted) {
            return $this->filesystem->copy($path, $newpath);
        }

        return false;
    }
}
flysystem/src/FileNotFoundException.php000064400000001263147361032630014325 0ustar00<?php

namespace League\Flysystem;

use Exception as BaseException;

class FileNotFoundException extends Exception
{
    /**
     * @var string
     */
    protected $path;

    /**
     * Constructor.
     *
     * @param string     $path
     * @param int        $code
     * @param \Exception $previous
     */
    public function __construct($path, $code = 0, BaseException $previous = null)
    {
        $this->path = $path;

        parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous);
    }

    /**
     * Get the path which was not found.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
flysystem/src/Util.php000064400000020610147361032630011024 0ustar00<?php

namespace League\Flysystem;

use League\Flysystem\Util\MimeType;
use LogicException;

class Util
{
    /**
     * Get normalized pathinfo.
     *
     * @param string $path
     *
     * @return array pathinfo
     */
    public static function pathinfo($path)
    {
        $pathinfo = compact('path');

        if ('' !== $dirname = dirname($path)) {
            $pathinfo['dirname'] = static::normalizeDirname($dirname);
        }

        $pathinfo['basename'] = static::basename($path);

        $pathinfo += pathinfo($pathinfo['basename']);

        return $pathinfo + ['dirname' => ''];
    }

    /**
     * Normalize a dirname return value.
     *
     * @param string $dirname
     *
     * @return string normalized dirname
     */
    public static function normalizeDirname($dirname)
    {
        return $dirname === '.' ? '' : $dirname;
    }

    /**
     * Get a normalized dirname from a path.
     *
     * @param string $path
     *
     * @return string dirname
     */
    public static function dirname($path)
    {
        return static::normalizeDirname(dirname($path));
    }

    /**
     * Map result arrays.
     *
     * @param array $object
     * @param array $map
     *
     * @return array mapped result
     */
    public static function map(array $object, array $map)
    {
        $result = [];

        foreach ($map as $from => $to) {
            if ( ! isset($object[$from])) {
                continue;
            }

            $result[$to] = $object[$from];
        }

        return $result;
    }

    /**
     * Normalize path.
     *
     * @param string $path
     *
     * @throws LogicException
     *
     * @return string
     */
    public static function normalizePath($path)
    {
        return static::normalizeRelativePath($path);
    }

    /**
     * Normalize relative directories in a path.
     *
     * @param string $path
     *
     * @throws LogicException
     *
     * @return string
     */
    public static function normalizeRelativePath($path)
    {
        $path = str_replace('\\', '/', $path);
        $path = static::removeFunkyWhiteSpace($path);

        $parts = [];

        foreach (explode('/', $path) as $part) {
            switch ($part) {
                case '':
                case '.':
                break;

            case '..':
                if (empty($parts)) {
                    throw new LogicException(
                        'Path is outside of the defined root, path: [' . $path . ']'
                    );
                }
                array_pop($parts);
                break;

            default:
                $parts[] = $part;
                break;
            }
        }

        return implode('/', $parts);
    }

    /**
     * Removes unprintable characters and invalid unicode characters.
     *
     * @param string $path
     *
     * @return string $path
     */
    protected static function removeFunkyWhiteSpace($path)
    {
        // We do this check in a loop, since removing invalid unicode characters
        // can lead to new characters being created.
        while (preg_match('#\p{C}+|^\./#u', $path)) {
            $path = preg_replace('#\p{C}+|^\./#u', '', $path);
        }

        return $path;
    }

    /**
     * Normalize prefix.
     *
     * @param string $prefix
     * @param string $separator
     *
     * @return string normalized path
     */
    public static function normalizePrefix($prefix, $separator)
    {
        return rtrim($prefix, $separator) . $separator;
    }

    /**
     * Get content size.
     *
     * @param string $contents
     *
     * @return int content size
     */
    public static function contentSize($contents)
    {
        return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents);
    }

    /**
     * Guess MIME Type based on the path of the file and it's content.
     *
     * @param string          $path
     * @param string|resource $content
     *
     * @return string|null MIME Type or NULL if no extension detected
     */
    public static function guessMimeType($path, $content)
    {
        $mimeType = MimeType::detectByContent($content);

        if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) {
            return $mimeType;
        }

        return MimeType::detectByFilename($path);
    }

    /**
     * Emulate directories.
     *
     * @param array $listing
     *
     * @return array listing with emulated directories
     */
    public static function emulateDirectories(array $listing)
    {
        $directories = [];
        $listedDirectories = [];

        foreach ($listing as $object) {
            list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories);
        }

        $directories = array_diff(array_unique($directories), array_unique($listedDirectories));

        foreach ($directories as $directory) {
            $listing[] = static::pathinfo($directory) + ['type' => 'dir'];
        }

        return $listing;
    }

    /**
     * Ensure a Config instance.
     *
     * @param null|array|Config $config
     *
     * @return Config config instance
     *
     * @throw  LogicException
     */
    public static function ensureConfig($config)
    {
        if ($config === null) {
            return new Config();
        }

        if ($config instanceof Config) {
            return $config;
        }

        if (is_array($config)) {
            return new Config($config);
        }

        throw new LogicException('A config should either be an array or a Flysystem\Config object.');
    }

    /**
     * Rewind a stream.
     *
     * @param resource $resource
     */
    public static function rewindStream($resource)
    {
        if (ftell($resource) !== 0 && static::isSeekableStream($resource)) {
            rewind($resource);
        }
    }

    public static function isSeekableStream($resource)
    {
        $metadata = stream_get_meta_data($resource);

        return $metadata['seekable'];
    }

    /**
     * Get the size of a stream.
     *
     * @param resource $resource
     *
     * @return int|null stream size
     */
    public static function getStreamSize($resource)
    {
        $stat = fstat($resource);

        if ( ! is_array($stat) || ! isset($stat['size'])) {
            return null;
        }

        return $stat['size'];
    }

    /**
     * Emulate the directories of a single object.
     *
     * @param array $object
     * @param array $directories
     * @param array $listedDirectories
     *
     * @return array
     */
    protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories)
    {
        if ($object['type'] === 'dir') {
            $listedDirectories[] = $object['path'];
        }

        if ( ! isset($object['dirname']) || trim($object['dirname']) === '') {
            return [$directories, $listedDirectories];
        }

        $parent = $object['dirname'];

        while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories)) {
            $directories[] = $parent;
            $parent = static::dirname($parent);
        }

        if (isset($object['type']) && $object['type'] === 'dir') {
            $listedDirectories[] = $object['path'];

            return [$directories, $listedDirectories];
        }

        return [$directories, $listedDirectories];
    }

    /**
     * Returns the trailing name component of the path.
     *
     * @param string $path
     *
     * @return string
     */
    private static function basename($path)
    {
        $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/';

        $path = rtrim($path, $separators);

        $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path);

        if (DIRECTORY_SEPARATOR === '/') {
            return $basename;
        }
        // @codeCoverageIgnoreStart
        // Extra Windows path munging. This is tested via AppVeyor, but code
        // coverage is not reported.

        // Handle relative paths with drive letters. c:file.txt.
        while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) {
            $basename = substr($basename, 2);
        }

        // Remove colon for standalone drive letter names.
        if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) {
            $basename = rtrim($basename, ':');
        }

        return $basename;
        // @codeCoverageIgnoreEnd
    }
}
flysystem/src/PluginInterface.php000064400000000530147361032630013165 0ustar00<?php

namespace League\Flysystem;

interface PluginInterface
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod();

    /**
     * Set the Filesystem object.
     *
     * @param FilesystemInterface $filesystem
     */
    public function setFilesystem(FilesystemInterface $filesystem);
}
flysystem/src/File.php000064400000010124147361032630010765 0ustar00<?php

namespace League\Flysystem;

/**
 * @deprecated
 */
class File extends Handler
{
    /**
     * Check whether the file exists.
     *
     * @return bool
     */
    public function exists()
    {
        return $this->filesystem->has($this->path);
    }

    /**
     * Read the file.
     *
     * @return string|false file contents
     */
    public function read()
    {
        return $this->filesystem->read($this->path);
    }

    /**
     * Read the file as a stream.
     *
     * @return resource|false file stream
     */
    public function readStream()
    {
        return $this->filesystem->readStream($this->path);
    }

    /**
     * Write the new file.
     *
     * @param string $content
     *
     * @return bool success boolean
     */
    public function write($content)
    {
        return $this->filesystem->write($this->path, $content);
    }

    /**
     * Write the new file using a stream.
     *
     * @param resource $resource
     *
     * @return bool success boolean
     */
    public function writeStream($resource)
    {
        return $this->filesystem->writeStream($this->path, $resource);
    }

    /**
     * Update the file contents.
     *
     * @param string $content
     *
     * @return bool success boolean
     */
    public function update($content)
    {
        return $this->filesystem->update($this->path, $content);
    }

    /**
     * Update the file contents with a stream.
     *
     * @param resource $resource
     *
     * @return bool success boolean
     */
    public function updateStream($resource)
    {
        return $this->filesystem->updateStream($this->path, $resource);
    }

    /**
     * Create the file or update if exists.
     *
     * @param string $content
     *
     * @return bool success boolean
     */
    public function put($content)
    {
        return $this->filesystem->put($this->path, $content);
    }

    /**
     * Create the file or update if exists using a stream.
     *
     * @param resource $resource
     *
     * @return bool success boolean
     */
    public function putStream($resource)
    {
        return $this->filesystem->putStream($this->path, $resource);
    }

    /**
     * Rename the file.
     *
     * @param string $newpath
     *
     * @return bool success boolean
     */
    public function rename($newpath)
    {
        if ($this->filesystem->rename($this->path, $newpath)) {
            $this->path = $newpath;

            return true;
        }

        return false;
    }

    /**
     * Copy the file.
     *
     * @param string $newpath
     *
     * @return File|false new file or false
     */
    public function copy($newpath)
    {
        if ($this->filesystem->copy($this->path, $newpath)) {
            return new File($this->filesystem, $newpath);
        }

        return false;
    }

    /**
     * Get the file's timestamp.
     *
     * @return string|false The timestamp or false on failure.
     */
    public function getTimestamp()
    {
        return $this->filesystem->getTimestamp($this->path);
    }

    /**
     * Get the file's mimetype.
     *
     * @return string|false The file mime-type or false on failure.
     */
    public function getMimetype()
    {
        return $this->filesystem->getMimetype($this->path);
    }

    /**
     * Get the file's visibility.
     *
     * @return string|false The visibility (public|private) or false on failure.
     */
    public function getVisibility()
    {
        return $this->filesystem->getVisibility($this->path);
    }

    /**
     * Get the file's metadata.
     *
     * @return array|false The file metadata or false on failure.
     */
    public function getMetadata()
    {
        return $this->filesystem->getMetadata($this->path);
    }

    /**
     * Get the file size.
     *
     * @return int|false The file size or false on failure.
     */
    public function getSize()
    {
        return $this->filesystem->getSize($this->path);
    }

    /**
     * Delete the file.
     *
     * @return bool success boolean
     */
    public function delete()
    {
        return $this->filesystem->delete($this->path);
    }
}
flysystem/SECURITY.md000064400000000654147361032630010406 0ustar00# Security Policy

## Supported Versions

| Version | Supported          |
| ------- | ------------------ |
| 1.0.x   | :white_check_mark: |
| 2.0.x   | :x:                |

## Reporting a Vulnerability

When you've encountered a security vulnerability, please disclose it securely.

The security process is described at: 
[https://flysystem.thephpleague.com/docs/security/](https://flysystem.thephpleague.com/docs/security/)

flysystem/LICENSE000064400000002047147361032630007620 0ustar00Copyright (c) 2013-2019 Frank de Jonge

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
uri/src/UriInfo.php000064400000015126147361032630010230 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri;

use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use TypeError;
use function explode;
use function implode;
use function preg_replace_callback;
use function rawurldecode;
use function sprintf;

final class UriInfo
{
    private const REGEXP_ENCODED_CHARS = ',%(2[D|E]|3[0-9]|4[1-9|A-F]|5[0-9|AF]|6[1-9|A-F]|7[0-9|E]),i';

    private const WHATWG_SPECIAL_SCHEMES = ['ftp' => 21, 'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443];

    /**
     * @codeCoverageIgnore
     */
    private function __construct()
    {
    }

    /**
     * @param Psr7UriInterface|UriInterface $uri
     */
    private static function emptyComponentValue($uri): ?string
    {
        return $uri instanceof Psr7UriInterface ? '' : null;
    }

    /**
     * Filter the URI object.
     *
     * To be valid an URI MUST implement at least one of the following interface:
     *     - League\Uri\UriInterface
     *     - Psr\Http\Message\UriInterface
     *
     * @param mixed $uri the URI to validate
     *
     * @throws TypeError if the URI object does not implements the supported interfaces.
     *
     * @return Psr7UriInterface|UriInterface
     */
    private static function filterUri($uri)
    {
        if ($uri instanceof Psr7UriInterface || $uri instanceof UriInterface) {
            return $uri;
        }

        throw new TypeError(sprintf('The uri must be a valid URI object received `%s`', is_object($uri) ? get_class($uri) : gettype($uri)));
    }

    /**
     * Normalize an URI for comparison.
     *
     * @param Psr7UriInterface|UriInterface $uri
     *
     * @return Psr7UriInterface|UriInterface
     */
    private static function normalize($uri)
    {
        $uri = self::filterUri($uri);
        $null = self::emptyComponentValue($uri);

        $path = $uri->getPath();
        if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme().$uri->getAuthority()) {
            $path = UriResolver::resolve($uri, $uri->withPath('')->withQuery($null))->getPath();
        }

        $query = $uri->getQuery();
        $fragment = $uri->getFragment();
        $fragmentOrig = $fragment;
        $pairs = null === $query ? [] : explode('&', $query);
        sort($pairs, SORT_REGULAR);

        $replace = static fn (array $matches): string => rawurldecode($matches[0]);

        $retval = preg_replace_callback(self::REGEXP_ENCODED_CHARS, $replace, [$path, implode('&', $pairs), $fragment]);
        if (null !== $retval) {
            [$path, $query, $fragment] = $retval + ['', $null, $null];
        }

        if ($null !== $uri->getAuthority() && '' === $path) {
            $path = '/';
        }

        return $uri
            ->withHost(Uri::createFromComponents(['host' => $uri->getHost()])->getHost())
            ->withPath($path)
            ->withQuery([] === $pairs ? $null : $query)
            ->withFragment($null === $fragmentOrig ? $fragmentOrig : $fragment);
    }

    /**
     * Tell whether the URI represents an absolute URI.
     *
     * @param Psr7UriInterface|UriInterface $uri
     */
    public static function isAbsolute($uri): bool
    {
        return self::emptyComponentValue($uri) !== self::filterUri($uri)->getScheme();
    }

    /**
     * Tell whether the URI represents a network path.
     *
     * @param Psr7UriInterface|UriInterface $uri
     */
    public static function isNetworkPath($uri): bool
    {
        $uri = self::filterUri($uri);
        $null = self::emptyComponentValue($uri);

        return $null === $uri->getScheme() && $null !== $uri->getAuthority();
    }

    /**
     * Tell whether the URI represents an absolute path.
     *
     * @param Psr7UriInterface|UriInterface $uri
     */
    public static function isAbsolutePath($uri): bool
    {
        $uri = self::filterUri($uri);
        $null = self::emptyComponentValue($uri);

        return $null === $uri->getScheme()
            && $null === $uri->getAuthority()
            && '/' === ($uri->getPath()[0] ?? '');
    }

    /**
     * Tell whether the URI represents a relative path.
     *
     * @param Psr7UriInterface|UriInterface $uri
     */
    public static function isRelativePath($uri): bool
    {
        $uri = self::filterUri($uri);
        $null = self::emptyComponentValue($uri);

        return $null === $uri->getScheme()
            && $null === $uri->getAuthority()
            && '/' !== ($uri->getPath()[0] ?? '');
    }

    /**
     * Tell whether both URI refers to the same document.
     *
     * @param Psr7UriInterface|UriInterface $uri
     * @param Psr7UriInterface|UriInterface $base_uri
     */
    public static function isSameDocument($uri, $base_uri): bool
    {
        $uri = self::normalize($uri);
        $base_uri = self::normalize($base_uri);

        return (string) $uri->withFragment($uri instanceof Psr7UriInterface ? '' : null)
            === (string) $base_uri->withFragment($base_uri instanceof Psr7UriInterface ? '' : null);
    }

    /**
     * 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)
     *
     * @param Psr7UriInterface|UriInterface $uri
     */
    public static function getOrigin($uri): ?string
    {
        $scheme = self::filterUri($uri)->getScheme();
        if ('blob' === $scheme) {
            $uri = Uri::createFromString($uri->getPath());
            $scheme = $uri->getScheme();
        }

        if (null === $scheme || !array_key_exists($scheme, self::WHATWG_SPECIAL_SCHEMES)) {
            return null;
        }

        $null = self::emptyComponentValue($uri);

        return (string) $uri->withFragment($null)->withQuery($null)->withPath('')->withUserInfo($null);
    }

    /**
     * @param Psr7UriInterface|UriInterface $uri
     * @param Psr7UriInterface|UriInterface $base_uri
     */
    public static function isCrossOrigin($uri, $base_uri): bool
    {
        return null === ($uriString = self::getOrigin(Uri::createFromUri($uri)))
            || null === ($baseUriString = self::getOrigin(Uri::createFromUri($base_uri)))
            || $uriString !== $baseUriString;
    }
}
uri/src/UriResolver.php000064400000025575147361032630011147 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri;

use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use TypeError;
use function array_pop;
use function array_reduce;
use function count;
use function end;
use function explode;
use function gettype;
use function implode;
use function in_array;
use function sprintf;
use function str_repeat;
use function strpos;
use function substr;

final class UriResolver
{
    /**
     * @var array<string,int>
     */
    const DOT_SEGMENTS = ['.' => 1, '..' => 1];

    /**
     * @codeCoverageIgnore
     */
    private function __construct()
    {
    }

    /**
     * Resolve an URI against a base URI using RFC3986 rules.
     *
     * If the first argument is a UriInterface the method returns a UriInterface object
     * If the first argument is a Psr7UriInterface the method returns a Psr7UriInterface object
     *
     * @param Psr7UriInterface|UriInterface $uri
     * @param Psr7UriInterface|UriInterface $base_uri
     *
     * @return Psr7UriInterface|UriInterface
     */
    public static function resolve($uri, $base_uri)
    {
        self::filterUri($uri);
        self::filterUri($base_uri);
        $null = $uri instanceof Psr7UriInterface ? '' : null;

        if ($null !== $uri->getScheme()) {
            return $uri
                ->withPath(self::removeDotSegments($uri->getPath()));
        }

        if ($null !== $uri->getAuthority()) {
            return $uri
                ->withScheme($base_uri->getScheme())
                ->withPath(self::removeDotSegments($uri->getPath()));
        }

        $user = $null;
        $pass = null;
        $userInfo = $base_uri->getUserInfo();
        if (null !== $userInfo) {
            [$user, $pass] = explode(':', $userInfo, 2) + [1 => null];
        }

        [$uri_path, $uri_query] = self::resolvePathAndQuery($uri, $base_uri);

        return $uri
            ->withPath(self::removeDotSegments($uri_path))
            ->withQuery($uri_query)
            ->withHost($base_uri->getHost())
            ->withPort($base_uri->getPort())
            ->withUserInfo((string) $user, $pass)
            ->withScheme($base_uri->getScheme())
        ;
    }

    /**
     * Filter the URI object.
     *
     * @param mixed $uri an URI object
     *
     * @throws TypeError if the URI object does not implements the supported interfaces.
     */
    private static function filterUri($uri): void
    {
        if (!$uri instanceof UriInterface && !$uri instanceof Psr7UriInterface) {
            throw new TypeError(sprintf('The uri must be a valid URI object received `%s`', gettype($uri)));
        }
    }

    /**
     * Remove dot segments from the URI path.
     */
    private static function removeDotSegments(string $path): string
    {
        if (false === strpos($path, '.')) {
            return $path;
        }

        $old_segments = explode('/', $path);
        $new_path = implode('/', array_reduce($old_segments, [UriResolver::class, 'reducer'], []));
        if (isset(self::DOT_SEGMENTS[end($old_segments)])) {
            $new_path .= '/';
        }

        // @codeCoverageIgnoreStart
        // added because some PSR-7 implementations do not respect RFC3986
        if (0 === strpos($path, '/') && 0 !== strpos($new_path, '/')) {
            return '/'.$new_path;
        }
        // @codeCoverageIgnoreEnd

        return $new_path;
    }

    /**
     * Remove dot segments.
     *
     * @return array<int, string>
     */
    private static function reducer(array $carry, string $segment): array
    {
        if ('..' === $segment) {
            array_pop($carry);

            return $carry;
        }

        if (!isset(self::DOT_SEGMENTS[$segment])) {
            $carry[] = $segment;
        }

        return $carry;
    }

    /**
     * Resolve an URI path and query component.
     *
     * @param Psr7UriInterface|UriInterface $uri
     * @param Psr7UriInterface|UriInterface $base_uri
     *
     * @return array{0:string, 1:string|null}
     */
    private static function resolvePathAndQuery($uri, $base_uri): array
    {
        $target_path = $uri->getPath();
        $target_query = $uri->getQuery();
        $null = $uri instanceof Psr7UriInterface ? '' : null;
        $baseNull = $base_uri instanceof Psr7UriInterface ? '' : null;

        if (0 === strpos($target_path, '/')) {
            return [$target_path, $target_query];
        }

        if ('' === $target_path) {
            if ($null === $target_query) {
                $target_query = $base_uri->getQuery();
            }

            $target_path = $base_uri->getPath();
            //@codeCoverageIgnoreStart
            //because some PSR-7 Uri implementations allow this RFC3986 forbidden construction
            if ($baseNull !== $base_uri->getAuthority() && 0 !== strpos($target_path, '/')) {
                $target_path = '/'.$target_path;
            }
            //@codeCoverageIgnoreEnd

            return [$target_path, $target_query];
        }

        $base_path = $base_uri->getPath();
        if ($baseNull !== $base_uri->getAuthority() && '' === $base_path) {
            $target_path = '/'.$target_path;
        }

        if ('' !== $base_path) {
            $segments = explode('/', $base_path);
            array_pop($segments);
            if ([] !== $segments) {
                $target_path = implode('/', $segments).'/'.$target_path;
            }
        }

        return [$target_path, $target_query];
    }

    /**
     * Relativize an URI according to a base URI.
     *
     * This method MUST retain the state of the submitted URI instance, and return
     * an 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.
     *
     * @param Psr7UriInterface|UriInterface $uri
     * @param Psr7UriInterface|UriInterface $base_uri
     *
     * @return Psr7UriInterface|UriInterface
     */
    public static function relativize($uri, $base_uri)
    {
        self::filterUri($uri);
        self::filterUri($base_uri);
        $uri = self::formatHost($uri);
        $base_uri = self::formatHost($base_uri);
        if (!self::isRelativizable($uri, $base_uri)) {
            return $uri;
        }

        $null = $uri instanceof Psr7UriInterface ? '' : null;
        $uri = $uri->withScheme($null)->withPort(null)->withUserInfo($null)->withHost($null);
        $target_path = $uri->getPath();
        if ($target_path !== $base_uri->getPath()) {
            return $uri->withPath(self::relativizePath($target_path, $base_uri->getPath()));
        }

        if (self::componentEquals('getQuery', $uri, $base_uri)) {
            return $uri->withPath('')->withQuery($null);
        }

        if ($null === $uri->getQuery()) {
            return $uri->withPath(self::formatPathWithEmptyBaseQuery($target_path));
        }

        return $uri->withPath('');
    }

    /**
     * Tells whether the component value from both URI object equals.
     *
     * @param Psr7UriInterface|UriInterface $uri
     * @param Psr7UriInterface|UriInterface $base_uri
     */
    private static function componentEquals(string $method, $uri, $base_uri): bool
    {
        return self::getComponent($method, $uri) === self::getComponent($method, $base_uri);
    }

    /**
     * Returns the component value from the submitted URI object.
     *
     * @param Psr7UriInterface|UriInterface $uri
     */
    private static function getComponent(string $method, $uri): ?string
    {
        $component = $uri->$method();
        if ($uri instanceof Psr7UriInterface && '' === $component) {
            return null;
        }

        return $component;
    }

    /**
     * Filter the URI object.
     *
     * @param Psr7UriInterface|UriInterface $uri
     *
     * @throws TypeError if the URI object does not implements the supported interfaces.
     *
     * @return Psr7UriInterface|UriInterface
     */
    private static function formatHost($uri)
    {
        if (!$uri instanceof Psr7UriInterface) {
            return $uri;
        }

        $host = $uri->getHost();
        if ('' === $host) {
            return $uri;
        }

        return $uri->withHost((string) Uri::createFromComponents(['host' => $host])->getHost());
    }

    /**
     * Tell whether the submitted URI object can be relativize.
     *
     * @param Psr7UriInterface|UriInterface $uri
     * @param Psr7UriInterface|UriInterface $base_uri
     */
    private static function isRelativizable($uri, $base_uri): bool
    {
        return !UriInfo::isRelativePath($uri)
            && self::componentEquals('getScheme', $uri, $base_uri)
            &&  self::componentEquals('getAuthority', $uri, $base_uri);
    }

    /**
     * Relative the URI for a authority-less target URI.
     */
    private static function relativizePath(string $path, string $basepath): string
    {
        $base_segments = self::getSegments($basepath);
        $target_segments = self::getSegments($path);
        $target_basename = array_pop($target_segments);
        array_pop($base_segments);
        foreach ($base_segments as $offset => $segment) {
            if (!isset($target_segments[$offset]) || $segment !== $target_segments[$offset]) {
                break;
            }
            unset($base_segments[$offset], $target_segments[$offset]);
        }
        $target_segments[] = $target_basename;

        return self::formatPath(
            str_repeat('../', count($base_segments)).implode('/', $target_segments),
            $basepath
        );
    }

    /**
     * returns the path segments.
     *
     * @return string[]
     */
    private static function getSegments(string $path): array
    {
        if ('' !== $path && '/' === $path[0]) {
            $path = substr($path, 1);
        }

        return explode('/', $path);
    }

    /**
     * Formatting the path to keep a valid URI.
     */
    private static function formatPath(string $path, string $basepath): string
    {
        if ('' === $path) {
            return in_array($basepath, ['', '/'], true) ? $basepath : './';
        }

        if (false === ($colon_pos = strpos($path, ':'))) {
            return $path;
        }

        $slash_pos = strpos($path, '/');
        if (false === $slash_pos || $colon_pos < $slash_pos) {
            return "./$path";
        }

        return $path;
    }

    /**
     * Formatting the path to keep a resolvable URI.
     */
    private static function formatPathWithEmptyBaseQuery(string $path): string
    {
        $target_segments = self::getSegments($path);
        /** @var string $basename */
        $basename = end($target_segments);

        return '' === $basename ? './' : $basename;
    }
}
uri/src/Http.php000064400000017040147361032630007571 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri;

use JsonSerializable;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\SyntaxError;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use function is_object;
use function is_scalar;
use function method_exists;
use function sprintf;

final class Http implements Psr7UriInterface, JsonSerializable
{
    private UriInterface $uri;

    private function __construct(UriInterface $uri)
    {
        $this->validate($uri);
        $this->uri = $uri;
    }

    /**
     * Validate the submitted uri against PSR-7 UriInterface.
     *
     * @throws SyntaxError if the given URI does not follow PSR-7 UriInterface rules
     */
    private function validate(UriInterface $uri): void
    {
        $scheme = $uri->getScheme();
        if (null === $scheme && '' === $uri->getHost()) {
            throw new SyntaxError(sprintf('an URI without scheme can not contains a empty host string according to PSR-7: %s', (string) $uri));
        }

        $port = $uri->getPort();
        if (null !== $port && ($port < 0 || $port > 65535)) {
            throw new SyntaxError(sprintf('The URI port is outside the established TCP and UDP port ranges: %s', (string) $uri->getPort()));
        }
    }

    /**
     * Static method called by PHP's var export.
     */
    public static function __set_state(array $components): self
    {
        return new self($components['uri']);
    }

    /**
     * Create a new instance from a string.
     *
     * @param string|mixed $uri
     */
    public static function createFromString($uri = ''): self
    {
        return new self(Uri::createFromString($uri));
    }

    /**
     * Create a new instance from a hash of parse_url parts.
     *
     * @param array $components a hash representation of the URI similar
     *                          to PHP parse_url function result
     */
    public static function createFromComponents(array $components): self
    {
        return new self(Uri::createFromComponents($components));
    }

    /**
     * Create a new instance from the environment.
     */
    public static function createFromServer(array $server): self
    {
        return new self(Uri::createFromServer($server));
    }

    /**
     * Create a new instance from a URI and a Base URI.
     *
     * The returned URI must be absolute.
     *
     * @param mixed $uri      the input URI to create
     * @param mixed $base_uri the base URI used for reference
     */
    public static function createFromBaseUri($uri, $base_uri = null): self
    {
        return new self(Uri::createFromBaseUri($uri, $base_uri));
    }

    /**
     * Create a new instance from a URI object.
     *
     * @param Psr7UriInterface|UriInterface $uri the input URI to create
     */
    public static function createFromUri($uri): self
    {
        if ($uri instanceof UriInterface) {
            return new self($uri);
        }

        return new self(Uri::createFromUri($uri));
    }

    /**
     * {@inheritDoc}
     */
    public function getScheme(): string
    {
        return (string) $this->uri->getScheme();
    }

    /**
     * {@inheritDoc}
     */
    public function getAuthority(): string
    {
        return (string) $this->uri->getAuthority();
    }

    /**
     * {@inheritDoc}
     */
    public function getUserInfo(): string
    {
        return (string) $this->uri->getUserInfo();
    }

    /**
     * {@inheritDoc}
     */
    public function getHost(): string
    {
        return (string) $this->uri->getHost();
    }

    /**
     * {@inheritDoc}
     */
    public function getPort(): ?int
    {
        return $this->uri->getPort();
    }

    /**
     * {@inheritDoc}
     */
    public function getPath(): string
    {
        return $this->uri->getPath();
    }

    /**
     * {@inheritDoc}
     */
    public function getQuery(): string
    {
        return (string) $this->uri->getQuery();
    }

    /**
     * {@inheritDoc}
     */
    public function getFragment(): string
    {
        return (string) $this->uri->getFragment();
    }

    /**
     * {@inheritDoc}
     */
    public function withScheme($scheme): self
    {
        /** @var string $scheme */
        $scheme = $this->filterInput($scheme);
        if ('' === $scheme) {
            $scheme = null;
        }

        $uri = $this->uri->withScheme($scheme);
        if ($uri->getScheme() === $this->uri->getScheme()) {
            return $this;
        }

        return new self($uri);
    }

    /**
     * Safely stringify input when possible.
     *
     * @param mixed $str the value to evaluate as a string
     *
     * @throws SyntaxError if the submitted data can not be converted to string
     *
     * @return string|mixed
     */
    private function filterInput($str)
    {
        if (is_scalar($str) || (is_object($str) && method_exists($str, '__toString'))) {
            return (string) $str;
        }

        return $str;
    }

    /**
     * {@inheritDoc}
     */
    public function withUserInfo($user, $password = null): self
    {
        /** @var string $user */
        $user = $this->filterInput($user);
        if ('' === $user) {
            $user = null;
        }

        $uri = $this->uri->withUserInfo($user, $password);
        if ($uri->getUserInfo() === $this->uri->getUserInfo()) {
            return $this;
        }

        return new self($uri);
    }

    /**
     * {@inheritDoc}
     */
    public function withHost($host): self
    {
        /** @var string $host */
        $host = $this->filterInput($host);
        if ('' === $host) {
            $host = null;
        }

        $uri = $this->uri->withHost($host);
        if ($uri->getHost() === $this->uri->getHost()) {
            return $this;
        }

        return new self($uri);
    }

    /**
     * {@inheritDoc}
     */
    public function withPort($port): self
    {
        $uri = $this->uri->withPort($port);
        if ($uri->getPort() === $this->uri->getPort()) {
            return $this;
        }

        return new self($uri);
    }

    /**
     * {@inheritDoc}
     */
    public function withPath($path): self
    {
        $uri = $this->uri->withPath($path);
        if ($uri->getPath() === $this->uri->getPath()) {
            return $this;
        }

        return new self($uri);
    }

    /**
     * {@inheritDoc}
     */
    public function withQuery($query): self
    {
        /** @var string $query */
        $query = $this->filterInput($query);
        if ('' === $query) {
            $query = null;
        }

        $uri = $this->uri->withQuery($query);
        if ($uri->getQuery() === $this->uri->getQuery()) {
            return $this;
        }

        return new self($uri);
    }

    /**
     * {@inheritDoc}
     */
    public function withFragment($fragment): self
    {
        /** @var string $fragment */
        $fragment = $this->filterInput($fragment);
        if ('' === $fragment) {
            $fragment = null;
        }

        $uri = $this->uri->withFragment($fragment);
        if ($uri->getFragment() === $this->uri->getFragment()) {
            return $this;
        }

        return new self($uri);
    }

    /**
     * {@inheritDoc}
     */
    public function __toString(): string
    {
        return $this->uri->__toString();
    }

    /**
     * {@inheritDoc}
     */
    public function jsonSerialize(): string
    {
        return $this->uri->__toString();
    }
}
uri/src/UriTemplate.php000064400000007756147361032630011122 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri;

use League\Uri\Contracts\UriException;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use League\Uri\UriTemplate\Template;
use League\Uri\UriTemplate\VariableBag;
use TypeError;

/**
 * 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
 *
 * Based on GuzzleHttp\UriTemplate class in Guzzle v6.5.
 * @link https://github.com/guzzle/guzzle/blob/6.5/src/UriTemplate.php
 */
final class UriTemplate
{
    private Template $template;
    private VariableBag $defaultVariables;

    /**
     * @param object|string $template a string or an object with the __toString method
     *
     * @throws TypeError                if the template is not a string or an object with the __toString method
     * @throws SyntaxError              if the template syntax is invalid
     * @throws TemplateCanNotBeExpanded if the template variables are invalid
     */
    public function __construct($template, array $defaultVariables = [])
    {
        $this->template = Template::createFromString($template);
        $this->defaultVariables = $this->filterVariables($defaultVariables);
    }

    public static function __set_state(array $properties): self
    {
        return new self($properties['template']->toString(), $properties['defaultVariables']->all());
    }

    /**
     * Filters out variables for the given template.
     *
     * @param array<string,string|array<string>> $variables
     */
    private function filterVariables(array $variables): VariableBag
    {
        $output = new VariableBag();
        foreach ($this->template->variableNames() as $name) {
            if (isset($variables[$name])) {
                $output->assign($name, $variables[$name]);
            }
        }

        return $output;
    }

    /**
     * The template string.
     */
    public function getTemplate(): string
    {
        return $this->template->toString();
    }

    /**
     * Returns the names of the variables in the template, in order.
     *
     * @return string[]
     */
    public function getVariableNames(): array
    {
        return $this->template->variableNames();
    }

    /**
     * Returns the default values used to expand the template.
     *
     * The returned list only contains variables whose name is part of the current template.
     *
     * @return array<string,string|array>
     */
    public function getDefaultVariables(): array
    {
        return $this->defaultVariables->all();
    }

    /**
     * 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.
     */
    public function withDefaultVariables(array $defaultDefaultVariables): self
    {
        return new self(
            $this->template->toString(),
            $this->filterVariables($defaultDefaultVariables)->all()
        );
    }

    /**
     * @throws TemplateCanNotBeExpanded if the variable contains nested array values
     * @throws UriException             if the resulting expansion can not be converted to a UriInterface instance
     */
    public function expand(array $variables = []): UriInterface
    {
        return Uri::createFromString(
            $this->template->expand(
                $this->filterVariables($variables)->replace($this->defaultVariables)
            )
        );
    }
}
uri/src/UriString.php000064400000036021147361032630010600 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri;

use League\Uri\Exceptions\IdnaConversionFailed;
use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\Idna\Idna;
use TypeError;
use function array_merge;
use function explode;
use function filter_var;
use function gettype;
use function inet_pton;
use function is_object;
use function is_scalar;
use function method_exists;
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
 */
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 an 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 array{
     *  scheme:?string,
     *  user:?string,
     *  pass:?string,
     *  host:?string,
     *  port:?int,
     *  path:?string,
     *  query:?string,
     *  fragment:?string
     * } $components
     */
    public static function build(array $components): string
    {
        $result = $components['path'] ?? '';
        if (isset($components['query'])) {
            $result .= '?'.$components['query'];
        }

        if (isset($components['fragment'])) {
            $result .= '#'.$components['fragment'];
        }

        $scheme = null;
        if (isset($components['scheme'])) {
            $scheme = $components['scheme'].':';
        }

        if (!isset($components['host'])) {
            return $scheme.$result;
        }

        $scheme .= '//';
        $authority = $components['host'];
        if (isset($components['port'])) {
            $authority .= ':'.$components['port'];
        }

        if (!isset($components['user'])) {
            return $scheme.$authority.$result;
        }

        $authority = '@'.$authority;
        if (!isset($components['pass'])) {
            return $scheme.$components['user'].$authority.$result;
        }

        return $scheme.$components['user'].':'.$components['pass'].$authority.$result;
    }

    /**
     * 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>
     *
     * @link https://tools.ietf.org/html/rfc3986
     *
     * @param mixed $uri any scalar or stringable object
     *
     * @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 array{
     *                scheme:?string,
     *                user:?string,
     *                pass:?string,
     *                host:?string,
     *                port:?int,
     *                path:string,
     *                query:?string,
     *                fragment:?string
     *                }
     */
    public static function parse($uri): array
    {
        if (is_object($uri) && method_exists($uri, '__toString')) {
            $uri = (string) $uri;
        }

        if (!is_scalar($uri)) {
            throw new TypeError(sprintf('The uri must be a scalar or a stringable object `%s` given', gettype($uri)));
        }

        $uri = (string) $uri;

        if (isset(self::URI_SCHORTCUTS[$uri])) {
            /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $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 array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $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 array{user:?string, pass:?string, host:?string, port:?int}
     */
    private static function parseAuthority(string $authority): array
    {
        $components = ['user' => null, 'pass' => null, 'host' => '', 'port' => null];
        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
    {
        if ('' === $port) {
            return null;
        }

        if (1 === preg_match('/^\d*$/', $port)) {
            return (int) $port;
        }

        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
    {
        if ('' === $host) {
            return $host;
        }

        if ('[' !== $host[0] || ']' !== substr($host, -1)) {
            return self::filterRegisteredName($host);
        }

        if (!self::isIpHost(substr($host, 1, -1))) {
            throw new SyntaxError(sprintf('Host `%s` is invalid : the IP host is malformed', $host));
        }

        return $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 IdnSupportMissing if IDN support or ICU requirement are not available or met.
     */
    private static function filterRegisteredName(string $host): string
    {
        $formatted_host = rawurldecode($host);
        if (1 === preg_match(self::REGEXP_REGISTERED_NAME, $formatted_host)) {
            return $host;
        }

        //to test IDN host non-ascii characters must be present in the host
        if (1 !== preg_match(self::REGEXP_IDN_PATTERN, $formatted_host)) {
            throw new SyntaxError(sprintf('Host `%s` is invalid : the host is not a valid registered name', $host));
        }

        $info = Idna::toAscii($host, Idna::IDNA2008_ASCII);
        if (0 !== $info->errors()) {
            throw IdnaConversionFailed::dueToIDNAError($host, $info);
        }

        return $host;
    }

    /**
     * Validates a IPv6/IPvfuture 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 $ip_host): bool
    {
        if (false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            return true;
        }

        if (1 === preg_match(self::REGEXP_IP_FUTURE, $ip_host, $matches)) {
            return !in_array($matches['version'], ['4', '6'], true);
        }

        $pos = strpos($ip_host, '%');
        if (false === $pos || 1 === preg_match(
            self::REGEXP_INVALID_HOST_CHARS,
            rawurldecode(substr($ip_host, $pos))
        )) {
            return false;
        }

        $ip_host = substr($ip_host, 0, $pos);

        return false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)
            && 0 === strpos((string) inet_pton($ip_host), self::ZONE_ID_ADDRESS_BLOCK);
    }
}
uri/src/HttpFactory.php000064400000001050147361032630011113 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri;

use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;

final class HttpFactory implements UriFactoryInterface
{
    public function createUri(string $uri = ''): UriInterface
    {
        return Http::createFromString($uri);
    }
}
uri/src/Exceptions/TemplateCanNotBeExpanded.php000064400000001536147361032630015574 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Exceptions;

use InvalidArgumentException;
use League\Uri\Contracts\UriException;

class TemplateCanNotBeExpanded extends InvalidArgumentException implements UriException
{
    public static function dueToUnableToProcessValueListWithPrefix(string $variableName): self
    {
        return new self('The ":" modifier can not be applied on "'.$variableName.'" since it is a list of values.');
    }

    public static function dueToNestedListOfValue(string $variableName): self
    {
        return new self('The "'.$variableName.'" can not be a nested list.');
    }
}
uri/src/Uri.php000064400000114112147361032630007407 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri;

use finfo;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\FileinfoSupportMissing;
use League\Uri\Exceptions\IdnaConversionFailed;
use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\Idna\Idna;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use TypeError;
use function array_filter;
use function array_map;
use function base64_decode;
use function base64_encode;
use function count;
use function explode;
use function file_get_contents;
use function filter_var;
use function implode;
use function in_array;
use function inet_pton;
use function is_object;
use function is_scalar;
use function method_exists;
use function preg_match;
use function preg_replace;
use function preg_replace_callback;
use function rawurlencode;
use function sprintf;
use function str_replace;
use function strlen;
use function strpos;
use function strspn;
use function strtolower;
use function substr;
use const FILEINFO_MIME;
use const FILTER_FLAG_IPV4;
use const FILTER_FLAG_IPV6;
use const FILTER_NULL_ON_FAILURE;
use const FILTER_VALIDATE_BOOLEAN;
use const FILTER_VALIDATE_IP;

final class Uri implements UriInterface
{
    /**
     * RFC3986 invalid characters.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-2.2
     *
     * @var string
     */
    private const REGEXP_INVALID_CHARS = '/[\x00-\x1f\x7f]/';

    /**
     * RFC3986 Sub delimiter characters regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-2.2
     *
     * @var string
     */
    private const REGEXP_CHARS_SUBDELIM = "\!\$&'\(\)\*\+,;\=%";

    /**
     * RFC3986 unreserved characters regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-2.3
     *
     * @var string
     */
    private const REGEXP_CHARS_UNRESERVED = 'A-Za-z0-9_\-\.~';

    /**
     * RFC3986 schema regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-3.1
     */
    private const REGEXP_SCHEME = ',^[a-z]([-a-z0-9+.]+)?$,i';

    /**
     * RFC3986 host identified by a registered name regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
     */
    private const REGEXP_HOST_REGNAME = '/^(
        (?<unreserved>[a-z0-9_~\-\.])|
        (?<sub_delims>[!$&\'()*+,;=])|
        (?<encoded>%[A-F0-9]{2})
    )+$/x';

    /**
     * RFC3986 delimiters of the generic URI components regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-2.2
     */
    private const REGEXP_HOST_GEN_DELIMS = '/[:\/?#\[\]@ ]/'; // Also includes space.

    /**
     * RFC3986 IPvFuture regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
     */
    private const REGEXP_HOST_IPFUTURE = '/^
        v(?<version>[A-F0-9])+\.
        (?:
            (?<unreserved>[a-z0-9_~\-\.])|
            (?<sub_delims>[!$&\'()*+,;=:])  # also include the : character
        )+
    $/ix';

    /**
     * RFC3986 IPvFuture host and port component.
     */
    private const REGEXP_HOST_PORT = ',^(?<host>(\[.*]|[^:])*)(:(?<port>[^/?#]*))?$,x';

    /**
     * Significant 10 bits of IP to detect Zone ID regular expression pattern.
     */
    private const HOST_ADDRESS_BLOCK = "\xfe\x80";

    /**
     * Regular expression pattern to for file URI.
     * <volume> contains the volume but not the volume separator.
     * The volume separator may be URL-encoded (`|` as `%7C`) by ::formatPath(),
     * so we account for that here.
     */
    private const REGEXP_FILE_PATH = ',^(?<delim>/)?(?<volume>[a-zA-Z])(?:[:|\|]|%7C)(?<rest>.*)?,';

    /**
     * Mimetype regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc2397
     */
    private const REGEXP_MIMETYPE = ',^\w+/[-.\w]+(?:\+[-.\w]+)?$,';

    /**
     * Base64 content regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc2397
     */
    private const REGEXP_BINARY = ',(;|^)base64$,';

    /**
     * Windows file path string regular expression pattern.
     * <root> contains both the volume and volume separator.
     */
    private const REGEXP_WINDOW_PATH = ',^(?<root>[a-zA-Z][:|\|]),';

    /**
     * Supported schemes and corresponding default port.
     *
     * @var array<string, int|null>
     */
    private const SCHEME_DEFAULT_PORT = [
        'data' => null,
        'file' => null,
        'ftp' => 21,
        'gopher' => 70,
        'http' => 80,
        'https' => 443,
        'ws' => 80,
        'wss' => 443,
    ];

    /**
     * URI validation methods per scheme.
     *
     * @var array<string>
     */
    private const SCHEME_VALIDATION_METHOD = [
        'data' => 'isUriWithSchemeAndPathOnly',
        'file' => 'isUriWithSchemeHostAndPathOnly',
        'ftp' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
        'gopher' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
        'http' => 'isNonEmptyHostUri',
        'https' => 'isNonEmptyHostUri',
        'ws' => 'isNonEmptyHostUriWithoutFragment',
        'wss' => 'isNonEmptyHostUriWithoutFragment',
    ];

    /**
     * All ASCII letters sorted by typical frequency of occurrence.
     *
     * @var string
     */
    private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";

    private ?string $scheme;
    private ?string $user_info;
    private ?string $host;
    private ?int $port;
    private ?string $authority;
    private string $path = '';
    private ?string $query;
    private ?string $fragment;
    private ?string $uri;

    private function __construct(
        ?string $scheme,
        ?string $user,
        ?string $pass,
        ?string $host,
        ?int $port,
        string $path,
        ?string $query,
        ?string $fragment
    ) {
        $this->scheme = $this->formatScheme($scheme);
        $this->user_info = $this->formatUserInfo($user, $pass);
        $this->host = $this->formatHost($host);
        $this->port = $this->formatPort($port);
        $this->authority = $this->setAuthority();
        $this->path = $this->formatPath($path);
        $this->query = $this->formatQueryAndFragment($query);
        $this->fragment = $this->formatQueryAndFragment($fragment);
        $this->assertValidState();
    }

    /**
     * Format the Scheme and Host component.
     *
     * @param  ?string     $scheme
     * @throws SyntaxError if the scheme is invalid
     */
    private function formatScheme(?string $scheme): ?string
    {
        if (null === $scheme) {
            return $scheme;
        }

        $formatted_scheme = strtolower($scheme);
        if (1 === preg_match(self::REGEXP_SCHEME, $formatted_scheme)) {
            return $formatted_scheme;
        }

        throw new SyntaxError(sprintf('The scheme `%s` is invalid.', $scheme));
    }

    /**
     * Set the UserInfo component.
     * @param ?string $user
     * @param ?string $password
     */
    private function formatUserInfo(?string $user, ?string $password): ?string
    {
        if (null === $user) {
            return $user;
        }

        static $user_pattern = '/(?:[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/';
        $user = preg_replace_callback($user_pattern, [Uri::class, 'urlEncodeMatch'], $user);
        if (null === $password) {
            return $user;
        }

        static $password_pattern = '/(?:[^%:'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/';

        return $user.':'.preg_replace_callback($password_pattern, [Uri::class, 'urlEncodeMatch'], $password);
    }

    /**
     * Returns the RFC3986 encoded string matched.
     */
    private static function urlEncodeMatch(array $matches): string
    {
        return rawurlencode($matches[0]);
    }

    /**
     * Validate and Format the Host component.
     * @param ?string $host
     */
    private function formatHost(?string $host): ?string
    {
        if (null === $host || '' === $host) {
            return $host;
        }

        if ('[' !== $host[0]) {
            return $this->formatRegisteredName($host);
        }

        return $this->formatIp($host);
    }

    /**
     * Validate and format a registered name.
     *
     * The host is converted to its ascii representation if needed
     *
     * @throws IdnSupportMissing if the submitted host required missing or misconfigured IDN support
     * @throws SyntaxError       if the submitted host is not a valid registered name
     */
    private function formatRegisteredName(string $host): string
    {
        $formatted_host = rawurldecode($host);
        if (1 === preg_match(self::REGEXP_HOST_REGNAME, $formatted_host)) {
            return $formatted_host;
        }

        if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, $formatted_host)) {
            throw new SyntaxError(sprintf('The host `%s` is invalid : a registered name can not contain URI delimiters or spaces', $host));
        }

        $info = Idna::toAscii($host, Idna::IDNA2008_ASCII);
        if (0 !== $info->errors()) {
            throw IdnaConversionFailed::dueToIDNAError($host, $info);
        }

        return $info->result();
    }

    /**
     * Validate and Format the IPv6/IPvfuture host.
     *
     * @throws SyntaxError if the submitted host is not a valid IP host
     */
    private function formatIp(string $host): string
    {
        $ip = substr($host, 1, -1);
        if (false !== filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            return $host;
        }

        if (1 === preg_match(self::REGEXP_HOST_IPFUTURE, $ip, $matches) && !in_array($matches['version'], ['4', '6'], true)) {
            return $host;
        }

        $pos = strpos($ip, '%');
        if (false === $pos) {
            throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
        }

        if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, rawurldecode(substr($ip, $pos)))) {
            throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
        }

        $ip = substr($ip, 0, $pos);
        if (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
        }

        //Only the address block fe80::/10 can have a Zone ID attach to
        //let's detect the link local significant 10 bits
        if (0 === strpos((string) inet_pton($ip), self::HOST_ADDRESS_BLOCK)) {
            return $host;
        }

        throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
    }

    /**
     * Format the Port component.
     *
     * @param object|null|int|string $port
     *
     * @throws SyntaxError
     */
    private function formatPort($port = null): ?int
    {
        if (null === $port || '' === $port) {
            return null;
        }

        if (!is_int($port) && !(is_string($port) && 1 === preg_match('/^\d*$/', $port))) {
            throw new SyntaxError('The port is expected to be an integer or a string representing an integer; '.gettype($port).' given.');
        }

        $port = (int) $port;
        if (0 > $port) {
            throw new SyntaxError(sprintf('The port `%s` is invalid', $port));
        }

        $defaultPort = self::SCHEME_DEFAULT_PORT[$this->scheme] ?? null;
        if ($defaultPort === $port) {
            return null;
        }

        return $port;
    }

    /**
     * {@inheritDoc}
     */
    public static function __set_state(array $components): self
    {
        $components['user'] = null;
        $components['pass'] = null;
        if (null !== $components['user_info']) {
            [$components['user'], $components['pass']] = explode(':', $components['user_info'], 2) + [1 => null];
        }

        return new self(
            $components['scheme'],
            $components['user'],
            $components['pass'],
            $components['host'],
            $components['port'],
            $components['path'],
            $components['query'],
            $components['fragment']
        );
    }

    /**
     * Create a new instance from a URI and a Base URI.
     *
     * The returned URI must be absolute.
     *
     * @param mixed      $uri      the input URI to create
     * @param null|mixed $base_uri the base URI used for reference
     */
    public static function createFromBaseUri($uri, $base_uri = null): UriInterface
    {
        if (!$uri instanceof UriInterface) {
            $uri = self::createFromString($uri);
        }

        if (null === $base_uri) {
            if (null === $uri->getScheme()) {
                throw new SyntaxError(sprintf('the URI `%s` must be absolute', (string) $uri));
            }

            if (null === $uri->getAuthority()) {
                return $uri;
            }

            /** @var UriInterface $uri */
            $uri = UriResolver::resolve($uri, $uri->withFragment(null)->withQuery(null)->withPath(''));

            return $uri;
        }

        if (!$base_uri instanceof UriInterface) {
            $base_uri = self::createFromString($base_uri);
        }

        if (null === $base_uri->getScheme()) {
            throw new SyntaxError(sprintf('the base URI `%s` must be absolute', (string) $base_uri));
        }

        /** @var UriInterface $uri */
        $uri = UriResolver::resolve($uri, $base_uri);

        return $uri;
    }

    /**
     * Create a new instance from a string.
     *
     * @param string|mixed $uri
     */
    public static function createFromString($uri = ''): self
    {
        $components = UriString::parse($uri);

        return new self(
            $components['scheme'],
            $components['user'],
            $components['pass'],
            $components['host'],
            $components['port'],
            $components['path'],
            $components['query'],
            $components['fragment']
        );
    }

    /**
     * Create a new instance from a hash representation of the URI similar
     * to PHP parse_url function result.
     */
    public static function createFromComponents(array $components = []): self
    {
        $components += [
            'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
            'port' => null, 'path' => '', 'query' => null, 'fragment' => null,
        ];

        return new self(
            $components['scheme'],
            $components['user'],
            $components['pass'],
            $components['host'],
            $components['port'],
            $components['path'],
            $components['query'],
            $components['fragment']
        );
    }

    /**
     * Create a new instance from a data file path.
     *
     * @param resource|null $context
     *
     * @throws FileinfoSupportMissing If ext/fileinfo is not installed
     * @throws SyntaxError            If the file does not exist or is not readable
     */
    public static function createFromDataPath(string $path, $context = null): self
    {
        static $finfo_support = null;
        $finfo_support = $finfo_support ?? class_exists(finfo::class);

        // @codeCoverageIgnoreStart
        if (!$finfo_support) {
            throw new FileinfoSupportMissing(sprintf('Please install ext/fileinfo to use the %s() method.', __METHOD__));
        }
        // @codeCoverageIgnoreEnd

        $file_args = [$path, false];
        $mime_args = [$path, FILEINFO_MIME];
        if (null !== $context) {
            $file_args[] = $context;
            $mime_args[] = $context;
        }

        $raw = @file_get_contents(...$file_args);
        if (false === $raw) {
            throw new SyntaxError(sprintf('The file `%s` does not exist or is not readable', $path));
        }

        $mimetype = (string) (new finfo(FILEINFO_MIME))->file(...$mime_args);

        return Uri::createFromComponents([
            'scheme' => 'data',
            'path' => str_replace(' ', '', $mimetype.';base64,'.base64_encode($raw)),
        ]);
    }

    /**
     * Create a new instance from a Unix path string.
     */
    public static function createFromUnixPath(string $uri = ''): self
    {
        $uri = implode('/', array_map('rawurlencode', explode('/', $uri)));
        if ('/' !== ($uri[0] ?? '')) {
            return Uri::createFromComponents(['path' => $uri]);
        }

        return Uri::createFromComponents(['path' => $uri, 'scheme' => 'file', 'host' => '']);
    }

    /**
     * Create a new instance from a local Windows path string.
     */
    public static function createFromWindowsPath(string $uri = ''): self
    {
        $root = '';
        if (1 === preg_match(self::REGEXP_WINDOW_PATH, $uri, $matches)) {
            $root = substr($matches['root'], 0, -1).':';
            $uri = substr($uri, strlen($root));
        }
        $uri = str_replace('\\', '/', $uri);
        $uri = implode('/', array_map('rawurlencode', explode('/', $uri)));

        //Local Windows absolute path
        if ('' !== $root) {
            return Uri::createFromComponents(['path' => '/'.$root.$uri, 'scheme' => 'file', 'host' => '']);
        }

        //UNC Windows Path
        if ('//' !== substr($uri, 0, 2)) {
            return Uri::createFromComponents(['path' => $uri]);
        }

        $parts = explode('/', substr($uri, 2), 2) + [1 => null];

        return Uri::createFromComponents(['host' => $parts[0], 'path' => '/'.$parts[1], 'scheme' => 'file']);
    }

    /**
     * Create a new instance from a URI object.
     *
     * @param Psr7UriInterface|UriInterface $uri the input URI to create
     */
    public static function createFromUri($uri): self
    {
        if ($uri instanceof UriInterface) {
            $user_info = $uri->getUserInfo();
            $user = null;
            $pass = null;
            if (null !== $user_info) {
                [$user, $pass] = explode(':', $user_info, 2) + [1 => null];
            }

            return new self(
                $uri->getScheme(),
                $user,
                $pass,
                $uri->getHost(),
                $uri->getPort(),
                $uri->getPath(),
                $uri->getQuery(),
                $uri->getFragment()
            );
        }

        if (!$uri instanceof Psr7UriInterface) {
            throw new TypeError(sprintf('The object must implement the `%s` or the `%s`', Psr7UriInterface::class, UriInterface::class));
        }

        $scheme = $uri->getScheme();
        if ('' === $scheme) {
            $scheme = null;
        }

        $fragment = $uri->getFragment();
        if ('' === $fragment) {
            $fragment = null;
        }

        $query = $uri->getQuery();
        if ('' === $query) {
            $query = null;
        }

        $host = $uri->getHost();
        if ('' === $host) {
            $host = null;
        }

        $user_info = $uri->getUserInfo();
        $user = null;
        $pass = null;
        if ('' !== $user_info) {
            [$user, $pass] = explode(':', $user_info, 2) + [1 => null];
        }

        return new self(
            $scheme,
            $user,
            $pass,
            $host,
            $uri->getPort(),
            $uri->getPath(),
            $query,
            $fragment
        );
    }

    /**
     * Create a new instance from the environment.
     */
    public static function createFromServer(array $server): self
    {
        [$user, $pass] = self::fetchUserInfo($server);
        [$host, $port] = self::fetchHostname($server);
        [$path, $query] = self::fetchRequestUri($server);

        return Uri::createFromComponents([
            'scheme' => self::fetchScheme($server),
            'user' => $user,
            'pass' => $pass,
            'host' => $host,
            'port' => $port,
            'path' => $path,
            'query' => $query,
        ]);
    }

    /**
     * Returns the environment scheme.
     */
    private static function fetchScheme(array $server): string
    {
        $server += ['HTTPS' => ''];
        $res = filter_var($server['HTTPS'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

        return false !== $res ? 'https' : 'http';
    }

    /**
     * Returns the environment user info.
     *
     * @return array{0:?string, 1:?string}
     */
    private static function fetchUserInfo(array $server): array
    {
        $server += ['PHP_AUTH_USER' => null, 'PHP_AUTH_PW' => null, 'HTTP_AUTHORIZATION' => ''];
        $user = $server['PHP_AUTH_USER'];
        $pass = $server['PHP_AUTH_PW'];
        if (0 === strpos(strtolower($server['HTTP_AUTHORIZATION']), 'basic')) {
            $userinfo = base64_decode(substr($server['HTTP_AUTHORIZATION'], 6), true);
            if (false === $userinfo) {
                throw new SyntaxError('The user info could not be detected');
            }
            [$user, $pass] = explode(':', $userinfo, 2) + [1 => null];
        }

        if (null !== $user) {
            $user = rawurlencode($user);
        }

        if (null !== $pass) {
            $pass = rawurlencode($pass);
        }

        return [$user, $pass];
    }

    /**
     * Returns the environment host.
     *
     * @throws SyntaxError If the host can not be detected
     *
     * @return array{0:string|null, 1:int|null}
     */
    private static function fetchHostname(array $server): array
    {
        $server += ['SERVER_PORT' => null];
        if (null !== $server['SERVER_PORT']) {
            $server['SERVER_PORT'] = (int) $server['SERVER_PORT'];
        }

        if (isset($server['HTTP_HOST']) && 1 === preg_match(self::REGEXP_HOST_PORT, $server['HTTP_HOST'], $matches)) {
            if (isset($matches['port'])) {
                $matches['port'] = (int) $matches['port'];
            }

            return [
                $matches['host'],
                $matches['port'] ?? $server['SERVER_PORT'],
            ];
        }

        if (!isset($server['SERVER_ADDR'])) {
            throw new SyntaxError('The host could not be detected');
        }

        if (false === filter_var($server['SERVER_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            $server['SERVER_ADDR'] = '['.$server['SERVER_ADDR'].']';
        }

        return [$server['SERVER_ADDR'], $server['SERVER_PORT']];
    }

    /**
     * Returns the environment path.
     *
     * @return array{0:?string, 1:?string}
     */
    private static function fetchRequestUri(array $server): array
    {
        $server += ['IIS_WasUrlRewritten' => null, 'UNENCODED_URL' => '', 'PHP_SELF' => '', 'QUERY_STRING' => null];
        if ('1' === $server['IIS_WasUrlRewritten'] && '' !== $server['UNENCODED_URL']) {
            /** @var array{0:?string, 1:?string} $retval */
            $retval = explode('?', $server['UNENCODED_URL'], 2) + [1 => null];

            return $retval;
        }

        if (isset($server['REQUEST_URI'])) {
            [$path, ] = explode('?', $server['REQUEST_URI'], 2);
            $query = ('' !== $server['QUERY_STRING']) ? $server['QUERY_STRING'] : null;

            return [$path, $query];
        }

        return [$server['PHP_SELF'], $server['QUERY_STRING']];
    }

    /**
     * Generate the URI authority part.
     */
    private function setAuthority(): ?string
    {
        $authority = null;
        if (null !== $this->user_info) {
            $authority = $this->user_info.'@';
        }

        if (null !== $this->host) {
            $authority .= $this->host;
        }

        if (null !== $this->port) {
            $authority .= ':'.$this->port;
        }

        return $authority;
    }

    /**
     * Format the Path component.
     */
    private function formatPath(string $path): string
    {
        $path = $this->formatDataPath($path);

        static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/}{]++|%(?![A-Fa-f0-9]{2}))/';

        $path = (string) preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $path);

        return $this->formatFilePath($path);
    }

    /**
     * Filter the Path component.
     *
     * @link https://tools.ietf.org/html/rfc2397
     *
     * @throws SyntaxError If the path is not compliant with RFC2397
     */
    private function formatDataPath(string $path): string
    {
        if ('data' !== $this->scheme) {
            return $path;
        }

        if ('' == $path) {
            return 'text/plain;charset=us-ascii,';
        }

        if (strlen($path) !== strspn($path, self::ASCII) || false === strpos($path, ',')) {
            throw new SyntaxError(sprintf('The path `%s` is invalid according to RFC2937', $path));
        }

        $parts = explode(',', $path, 2) + [1 => null];
        $mediatype = explode(';', (string) $parts[0], 2) + [1 => null];
        $data = (string) $parts[1];
        $mimetype = $mediatype[0];
        if (null === $mimetype || '' === $mimetype) {
            $mimetype = 'text/plain';
        }

        $parameters = $mediatype[1];
        if (null === $parameters || '' === $parameters) {
            $parameters = 'charset=us-ascii';
        }

        $this->assertValidPath($mimetype, $parameters, $data);

        return $mimetype.';'.$parameters.','.$data;
    }

    /**
     * Assert the path is a compliant with RFC2397.
     *
     * @link https://tools.ietf.org/html/rfc2397
     *
     * @throws SyntaxError If the mediatype or the data are not compliant with the RFC2397
     */
    private function assertValidPath(string $mimetype, string $parameters, string $data): void
    {
        if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) {
            throw new SyntaxError(sprintf('The path mimetype `%s` is invalid', $mimetype));
        }

        $is_binary = 1 === preg_match(self::REGEXP_BINARY, $parameters, $matches);
        if ($is_binary) {
            $parameters = substr($parameters, 0, - strlen($matches[0]));
        }

        $res = array_filter(array_filter(explode(';', $parameters), [$this, 'validateParameter']));
        if ([] !== $res) {
            throw new SyntaxError(sprintf('The path paremeters `%s` is invalid', $parameters));
        }

        if (!$is_binary) {
            return;
        }

        $res = base64_decode($data, true);
        if (false === $res || $data !== base64_encode($res)) {
            throw new SyntaxError(sprintf('The path data `%s` is invalid', $data));
        }
    }

    /**
     * Validate mediatype parameter.
     */
    private function validateParameter(string $parameter): bool
    {
        $properties = explode('=', $parameter);

        return 2 != count($properties) || 'base64' === strtolower($properties[0]);
    }

    /**
     * Format path component for file scheme.
     */
    private function formatFilePath(string $path): string
    {
        if ('file' !== $this->scheme) {
            return $path;
        }

        $replace = static function (array $matches): string {
            return $matches['delim'].$matches['volume'].':'.$matches['rest'];
        };

        return (string) preg_replace_callback(self::REGEXP_FILE_PATH, $replace, $path);
    }

    /**
     * Format the Query or the Fragment component.
     *
     * Returns a array containing:
     * <ul>
     * <li> the formatted component (a string or null)</li>
     * <li> a boolean flag telling wether the delimiter is to be added to the component
     * when building the URI string representation</li>
     * </ul>
     *
     * @param ?string $component
     */
    private function formatQueryAndFragment(?string $component): ?string
    {
        if (null === $component || '' === $component) {
            return $component;
        }

        static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/';
        return preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $component);
    }

    /**
     * assert the URI internal state is valid.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-3
     * @link https://tools.ietf.org/html/rfc3986#section-3.3
     *
     * @throws SyntaxError if the URI is in an invalid state according to RFC3986
     * @throws SyntaxError if the URI is in an invalid state according to scheme specific rules
     */
    private function assertValidState(): void
    {
        if (null !== $this->authority && ('' !== $this->path && '/' !== $this->path[0])) {
            throw new SyntaxError('If an authority is present the path must be empty or start with a `/`.');
        }

        if (null === $this->authority && 0 === strpos($this->path, '//')) {
            throw new SyntaxError(sprintf('If there is no authority the path `%s` can not start with a `//`.', $this->path));
        }

        $pos = strpos($this->path, ':');
        if (null === $this->authority
            && null === $this->scheme
            && false !== $pos
            && false === strpos(substr($this->path, 0, $pos), '/')
        ) {
            throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.');
        }

        $validationMethod = self::SCHEME_VALIDATION_METHOD[$this->scheme] ?? null;
        if (null === $validationMethod || true === $this->$validationMethod()) {
            $this->uri = null;

            return;
        }

        throw new SyntaxError(sprintf('The uri `%s` is invalid for the `%s` scheme.', (string) $this, $this->scheme));
    }

    /**
     * URI validation for URI schemes which allows only scheme and path components.
     */
    private function isUriWithSchemeAndPathOnly(): bool
    {
        return null === $this->authority
            && null === $this->query
            && null === $this->fragment;
    }

    /**
     * URI validation for URI schemes which allows only scheme, host and path components.
     */
    private function isUriWithSchemeHostAndPathOnly(): bool
    {
        return null === $this->user_info
            && null === $this->port
            && null === $this->query
            && null === $this->fragment
            && !('' != $this->scheme && null === $this->host);
    }

    /**
     * URI validation for URI schemes which disallow the empty '' host.
     */
    private function isNonEmptyHostUri(): bool
    {
        return '' !== $this->host
            && !(null !== $this->scheme && null === $this->host);
    }

    /**
     * URI validation for URIs schemes which disallow the empty '' host
     * and forbids the fragment component.
     */
    private function isNonEmptyHostUriWithoutFragment(): bool
    {
        return $this->isNonEmptyHostUri() && null === $this->fragment;
    }

    /**
     * URI validation for URIs schemes which disallow the empty '' host
     * and forbids fragment and query components.
     */
    private function isNonEmptyHostUriWithoutFragmentAndQuery(): bool
    {
        return $this->isNonEmptyHostUri() && null === $this->fragment && null === $this->query;
    }

    /**
     * Generate the URI string representation from its components.
     *
     * @link https://tools.ietf.org/html/rfc3986#section-5.3
     *
     * @param ?string $scheme
     * @param ?string $authority
     * @param ?string $query
     * @param ?string $fragment
     */
    private function getUriString(
        ?string $scheme,
        ?string $authority,
        string $path,
        ?string $query,
        ?string $fragment
    ): string {
        if (null !== $scheme) {
            $scheme = $scheme.':';
        }

        if (null !== $authority) {
            $authority = '//'.$authority;
        }

        if (null !== $query) {
            $query = '?'.$query;
        }

        if (null !== $fragment) {
            $fragment = '#'.$fragment;
        }

        return $scheme.$authority.$path.$query.$fragment;
    }

    public function toString(): string
    {
        $this->uri = $this->uri ?? $this->getUriString(
            $this->scheme,
            $this->authority,
            $this->path,
            $this->query,
            $this->fragment
        );

        return $this->uri;
    }

    /**
     * {@inheritDoc}
     */
    public function __toString(): string
    {
        return $this->toString();
    }

    /**
     * {@inheritDoc}
     */
    public function jsonSerialize(): string
    {
        return $this->toString();
    }

    /**
     * {@inheritDoc}
     *
     * @return array{scheme:?string, user_info:?string, host:?string, port:?int, path:string, query:?string, fragment:?string}
     */
    public function __debugInfo(): array
    {
        return [
            'scheme' => $this->scheme,
            'user_info' => isset($this->user_info) ? preg_replace(',:(.*).?$,', ':***', $this->user_info) : null,
            'host' => $this->host,
            'port' => $this->port,
            'path' => $this->path,
            'query' => $this->query,
            'fragment' => $this->fragment,
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function getScheme(): ?string
    {
        return $this->scheme;
    }

    /**
     * {@inheritDoc}
     */
    public function getAuthority(): ?string
    {
        return $this->authority;
    }

    /**
     * {@inheritDoc}
     */
    public function getUserInfo(): ?string
    {
        return $this->user_info;
    }

    /**
     * {@inheritDoc}
     */
    public function getHost(): ?string
    {
        return $this->host;
    }

    /**
     * {@inheritDoc}
     */
    public function getPort(): ?int
    {
        return $this->port;
    }

    /**
     * {@inheritDoc}
     */
    public function getPath(): string
    {
        return $this->path;
    }

    /**
     * {@inheritDoc}
     */
    public function getQuery(): ?string
    {
        return $this->query;
    }

    /**
     * {@inheritDoc}
     */
    public function getFragment(): ?string
    {
        return $this->fragment;
    }

    /**
     * {@inheritDoc}
     */
    public function withScheme($scheme): UriInterface
    {
        $scheme = $this->formatScheme($this->filterString($scheme));
        if ($scheme === $this->scheme) {
            return $this;
        }

        $clone = clone $this;
        $clone->scheme = $scheme;
        $clone->port = $clone->formatPort($clone->port);
        $clone->authority = $clone->setAuthority();
        $clone->assertValidState();

        return $clone;
    }

    /**
     * Filter a string.
     *
     * @param mixed $str the value to evaluate as a string
     *
     * @throws SyntaxError if the submitted data can not be converted to string
     */
    private function filterString($str): ?string
    {
        if (null === $str) {
            return $str;
        }

        if (is_object($str) && method_exists($str, '__toString')) {
            $str = (string) $str;
        }

        if (!is_scalar($str)) {
            throw new SyntaxError(sprintf('The component must be a string, a scalar or a stringable object; `%s` given.', gettype($str)));
        }

        $str = (string) $str;
        if (1 !== preg_match(self::REGEXP_INVALID_CHARS, $str)) {
            return $str;
        }

        throw new SyntaxError(sprintf('The component `%s` contains invalid characters.', $str));
    }

    /**
     * {@inheritDoc}
     */
    public function withUserInfo($user, $password = null): UriInterface
    {
        $user_info = null;
        $user = $this->filterString($user);
        if (null !== $password) {
            $password = $this->filterString($password);
        }

        if ('' !== $user) {
            $user_info = $this->formatUserInfo($user, $password);
        }

        if ($user_info === $this->user_info) {
            return $this;
        }

        $clone = clone $this;
        $clone->user_info = $user_info;
        $clone->authority = $clone->setAuthority();
        $clone->assertValidState();

        return $clone;
    }

    /**
     * {@inheritDoc}
     */
    public function withHost($host): UriInterface
    {
        $host = $this->formatHost($this->filterString($host));
        if ($host === $this->host) {
            return $this;
        }

        $clone = clone $this;
        $clone->host = $host;
        $clone->authority = $clone->setAuthority();
        $clone->assertValidState();

        return $clone;
    }

    /**
     * {@inheritDoc}
     */
    public function withPort($port): UriInterface
    {
        $port = $this->formatPort($port);
        if ($port === $this->port) {
            return $this;
        }

        $clone = clone $this;
        $clone->port = $port;
        $clone->authority = $clone->setAuthority();
        $clone->assertValidState();

        return $clone;
    }

    /**
     * {@inheritDoc}
     *
     * @param string|object $path
     */
    public function withPath($path): UriInterface
    {
        $path = $this->filterString($path);
        if (null === $path) {
            throw new TypeError('A path must be a string NULL given.');
        }

        $path = $this->formatPath($path);
        if ($path === $this->path) {
            return $this;
        }

        $clone = clone $this;
        $clone->path = $path;
        $clone->assertValidState();

        return $clone;
    }

    /**
     * {@inheritDoc}
     */
    public function withQuery($query): UriInterface
    {
        $query = $this->formatQueryAndFragment($this->filterString($query));
        if ($query === $this->query) {
            return $this;
        }

        $clone = clone $this;
        $clone->query = $query;
        $clone->assertValidState();

        return $clone;
    }

    /**
     * {@inheritDoc}
     */
    public function withFragment($fragment): UriInterface
    {
        $fragment = $this->formatQueryAndFragment($this->filterString($fragment));
        if ($fragment === $this->fragment) {
            return $this;
        }

        $clone = clone $this;
        $clone->fragment = $fragment;
        $clone->assertValidState();

        return $clone;
    }
}
uri/src/UriTemplate/Template.php000064400000007521147361032630012663 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\UriTemplate;

use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use TypeError;
use function array_merge;
use function array_unique;
use function gettype;
use function is_object;
use function is_string;
use function method_exists;
use function preg_match_all;
use function preg_replace;
use function sprintf;
use function strpos;
use const PREG_SET_ORDER;

final class Template
{
    /**
     * Expression regular expression pattern.
     */
    private const REGEXP_EXPRESSION_DETECTOR = '/\{[^\}]*\}/x';

    private string $template;
    /** @var array<string, Expression> */
    private array $expressions = [];
    /** @var array<string> */
    private array $variableNames;

    private function __construct(string $template, Expression ...$expressions)
    {
        $this->template = $template;
        $variableNames = [];
        foreach ($expressions as $expression) {
            $this->expressions[$expression->toString()] = $expression;
            $variableNames[] = $expression->variableNames();
        }
        $this->variableNames = array_unique(array_merge([], ...$variableNames));
    }

    /**
     * {@inheritDoc}
     */
    public static function __set_state(array $properties): self
    {
        return new self($properties['template'], ...array_values($properties['expressions']));
    }

    /**
     * @param object|string $template a string or an object with the __toString method
     *
     * @throws TypeError   if the template is not a string or an object with the __toString method
     * @throws SyntaxError if the template contains invalid expressions
     * @throws SyntaxError if the template contains invalid variable specification
     */
    public static function createFromString($template): self
    {
        if (is_object($template) && method_exists($template, '__toString')) {
            $template = (string) $template;
        }

        if (!is_string($template)) {
            throw new TypeError(sprintf('The template must be a string or a stringable object %s given.', gettype($template)));
        }

        /** @var string $remainder */
        $remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
        if (false !== strpos($remainder, '{') || false !== strpos($remainder, '}')) {
            throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
        }

        $names = [];
        preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $findings, PREG_SET_ORDER);
        $arguments = [];
        foreach ($findings as $finding) {
            if (!isset($names[$finding[0]])) {
                $arguments[] = Expression::createFromString($finding[0]);
                $names[$finding[0]] = 1;
            }
        }

        return new self($template, ...$arguments);
    }

    public function toString(): string
    {
        return $this->template;
    }

    /**
     * @return array<string>
     */
    public function variableNames(): array
    {
        return $this->variableNames;
    }

    /**
     * @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(VariableBag $variables): string
    {
        $uriString = $this->template;
        /** @var Expression $expression */
        foreach ($this->expressions as $pattern => $expression) {
            $uriString = str_replace($pattern, $expression->expand($variables), $uriString);
        }

        return $uriString;
    }
}
uri/src/UriTemplate/Expression.php000064400000023575147361032630013256 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\UriTemplate;

use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use function array_filter;
use function array_keys;
use function array_map;
use function array_unique;
use function explode;
use function implode;
use function preg_match;
use function rawurlencode;
use function str_replace;
use function strpos;
use function substr;

final class Expression
{
    /**
     * Expression regular expression pattern.
     *
     * @link https://tools.ietf.org/html/rfc6570#section-2.2
     */
    private const REGEXP_EXPRESSION = '/^\{
        (?:
            (?<operator>[\.\/;\?&\=,\!@\|\+#])?
            (?<variables>[^\}]*)
        )
    \}$/x';

    /**
     * Reserved Operator characters.
     *
     * @link https://tools.ietf.org/html/rfc6570#section-2.2
     */
    private const RESERVED_OPERATOR = '=,!@|';

    /**
     * Processing behavior according to the expression type operator.
     *
     * @link https://tools.ietf.org/html/rfc6570#appendix-A
     */
    private const OPERATOR_HASH_LOOKUP = [
        ''  => ['prefix' => '',  'joiner' => ',', 'query' => false],
        '+' => ['prefix' => '',  'joiner' => ',', 'query' => false],
        '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
        '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
        '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
        ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
        '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
        '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true],
    ];

    private string $operator;
    /** @var array<VarSpecifier> */
    private array $varSpecifiers;
    private string $joiner;
    /** @var array<string> */
    private array $variableNames;
    private string $expressionString;

    private function __construct(string $operator, VarSpecifier ...$varSpecifiers)
    {
        $this->operator = $operator;
        $this->varSpecifiers = $varSpecifiers;
        $this->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner'];
        $this->variableNames = $this->setVariableNames();
        $this->expressionString = $this->setExpressionString();
    }

    /**
     * @return array<string>
     */
    private function setVariableNames(): array
    {
        return array_unique(array_map(
            static fn (VarSpecifier $varSpecifier): string => $varSpecifier->name(),
            $this->varSpecifiers
        ));
    }

    private function setExpressionString(): string
    {
        $varSpecifierString = implode(',', array_map(
            static fn (VarSpecifier $variable): string => $variable->toString(),
            $this->varSpecifiers
        ));

        return '{'.$this->operator.$varSpecifierString.'}';
    }

    /**
     * {@inheritDoc}
     */
    public static function __set_state(array $properties): self
    {
        return new self($properties['operator'], ...$properties['varSpecifiers']);
    }

    /**
     * @throws SyntaxError if the expression is invalid
     * @throws SyntaxError if the operator used in the expression is invalid
     * @throws SyntaxError if the variable specifiers is invalid
     */
    public static function createFromString(string $expression): self
    {
        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'] && false !== strpos(self::RESERVED_OPERATOR, $parts['operator'])) {
            throw new SyntaxError('The operator used in the expression "'.$expression.'" is reserved.');
        }

        return new Expression($parts['operator'], ...array_map(
            static fn (string $varSpec): VarSpecifier => VarSpecifier::createFromString($varSpec),
            explode(',', $parts['variables'])
        ));
    }

    /**
     * Returns the expression string representation.
     *
     */
    public function toString(): string
    {
        return $this->expressionString;
    }

    /**
     * @return array<string>
     */
    public function variableNames(): array
    {
        return $this->variableNames;
    }

    public function expand(VariableBag $variables): string
    {
        $parts = [];
        foreach ($this->varSpecifiers as $varSpecifier) {
            $parts[] = $this->replace($varSpecifier, $variables);
        }

        $expanded = implode($this->joiner, array_filter($parts, static fn ($value): bool => '' !== $value));
        if ('' === $expanded) {
            return $expanded;
        }

        $prefix = self::OPERATOR_HASH_LOOKUP[$this->operator]['prefix'];
        if ('' === $prefix) {
            return $expanded;
        }

        return $prefix.$expanded;
    }

    /**
     * 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
     */
    private function replace(VarSpecifier $varSpec, VariableBag $variables): string
    {
        $value = $variables->fetch($varSpec->name());
        if (null === $value) {
            return '';
        }

        $useQuery = self::OPERATOR_HASH_LOOKUP[$this->operator]['query'];
        [$expanded, $actualQuery] = $this->inject($value, $varSpec, $useQuery);
        if (!$actualQuery) {
            return $expanded;
        }

        if ('&' !== $this->joiner && '' === $expanded) {
            return $varSpec->name();
        }

        return $varSpec->name().'='.$expanded;
    }

    /**
     * @param string|array<string> $value
     *
     * @return array{0:string, 1:bool}
     */
    private function inject($value, VarSpecifier $varSpec, bool $useQuery): array
    {
        if (is_string($value)) {
            return $this->replaceString($value, $varSpec, $useQuery);
        }

        return $this->replaceList($value, $varSpec, $useQuery);
    }

    /**
     * Expands an expression using a string value.
     *
     * @return array{0:string, 1:bool}
     */
    private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery): array
    {
        if (':' === $varSpec->modifier()) {
            $value = substr($value, 0, $varSpec->position());
        }

        $expanded = rawurlencode($value);
        if ('+' === $this->operator || '#' === $this->operator) {
            return [$this->decodeReserved($expanded), $useQuery];
        }

        return [$expanded, $useQuery];
    }

    /**
     * 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, bool $useQuery): array
    {
        if ([] === $value) {
            return ['', false];
        }

        if (':' === $varSpec->modifier()) {
            throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name());
        }

        $pairs = [];
        $isAssoc = $this->isAssoc($value);
        foreach ($value as $key => $var) {
            if ($isAssoc) {
                $key = rawurlencode((string) $key);
            }

            $var = rawurlencode($var);
            if ('+' === $this->operator || '#' === $this->operator) {
                $var = $this->decodeReserved($var);
            }

            if ('*' === $varSpec->modifier()) {
                if ($isAssoc) {
                    $var = $key.'='.$var;
                } elseif ($key > 0 && $useQuery) {
                    $var = $varSpec->name().'='.$var;
                }
            }

            $pairs[$key] = $var;
        }

        if ('*' === $varSpec->modifier()) {
            if ($isAssoc) {
                // Don't prepend the value name when using the explode
                // modifier with an associative array.
                $useQuery = false;
            }

            return [implode($this->joiner, $pairs), $useQuery];
        }

        if ($isAssoc) {
            // 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.
            foreach ($pairs as $offset => &$data) {
                $data = $offset.','.$data;
            }

            unset($data);
        }

        return [implode(',', $pairs), $useQuery];
    }

    /**
     * Determines if an array is associative.
     *
     * This makes the assumption that input arrays are sequences or hashes.
     * This assumption is a trade-off for accuracy in favor of speed, but it
     * should work in almost every case where input is supplied for a URI
     * template.
     */
    private function isAssoc(array $array): bool
    {
        return [] !== $array && 0 !== array_keys($array)[0];
    }

    /**
     * Removes percent encoding on reserved characters (used with + and # modifiers).
     */
    private function decodeReserved(string $str): string
    {
        static $delimiters = [
            ':', '/', '?', '#', '[', ']', '@', '!', '$',
            '&', '\'', '(', ')', '*', '+', ',', ';', '=',
        ];

        static $delimitersEncoded = [
            '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24',
            '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D',
        ];

        return str_replace($delimitersEncoded, $delimiters, $str);
    }
}
uri/src/UriTemplate/VarSpecifier.php000064400000004663147361032630013476 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\UriTemplate;

use League\Uri\Exceptions\SyntaxError;
use function preg_match;

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+)|\*)?
    $/x';

    private string $name;
    private string $modifier;
    private int $position;

    private function __construct(string $name, string $modifier, int $position)
    {
        $this->name = $name;
        $this->modifier = $modifier;
        $this->position = $position;
    }

    /**
     * {@inheritDoc}
     */
    public static function __set_state(array $properties): self
    {
        return new self($properties['name'], $properties['modifier'], $properties['position']);
    }

    public static function createFromString(string $specification): self
    {
        if (1 !== preg_match(self::REGEXP_VARSPEC, $specification, $parsed)) {
            throw new SyntaxError('The variable specification "'.$specification.'" is invalid.');
        }

        $parsed += ['modifier' => '', 'position' => ''];
        if ('' !== $parsed['position']) {
            $parsed['position'] = (int) $parsed['position'];
            $parsed['modifier'] = ':';
        }

        if ('' === $parsed['position']) {
            $parsed['position'] = 0;
        }

        if (10000 <= $parsed['position']) {
            throw new SyntaxError('The variable specification "'.$specification.'" is invalid the position modifier must be lower than 10000.');
        }

        return new self($parsed['name'], $parsed['modifier'], $parsed['position']);
    }

    public function toString(): string
    {
        if (0 < $this->position) {
            return $this->name.$this->modifier.$this->position;
        }

        return $this->name.$this->modifier;
    }

    public function name(): string
    {
        return $this->name;
    }

    public function modifier(): string
    {
        return $this->modifier;
    }

    public function position(): int
    {
        return $this->position;
    }
}
uri/src/UriTemplate/VariableBag.php000064400000005541147361032630013247 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\UriTemplate;

use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use TypeError;
use function gettype;
use function is_array;
use function is_bool;
use function is_object;
use function is_scalar;
use function method_exists;
use function sprintf;

final class VariableBag
{
    /**
     * @var array<string,string|array<string>>
     */
    private array $variables = [];

    /**
     * @param iterable<string,string|bool|int|float|array<string|bool|int|float>> $variables
     */
    public function __construct(iterable $variables = [])
    {
        foreach ($variables as $name => $value) {
            $this->assign($name, $value);
        }
    }

    public static function __set_state(array $properties): self
    {
        return new self($properties['variables']);
    }

    /**
     * @return array<string,string|array<string>>
     */
    public function all(): array
    {
        return $this->variables;
    }

    /**
     * Fetches the variable value if none found returns null.
     *
     * @return null|string|array<string>
     */
    public function fetch(string $name)
    {
        return $this->variables[$name] ?? null;
    }

    /**
     * @param string|bool|int|float|array<string|bool|int|float> $value
     */
    public function assign(string $name, $value): void
    {
        $this->variables[$name] = $this->normalizeValue($value, $name, true);
    }

    /**
     * @param mixed $value the value to be expanded
     *
     * @throws TemplateCanNotBeExpanded if the value contains nested list
     *
     * @return string|array<string>
     */
    private function normalizeValue($value, string $name, bool $isNestedListAllowed)
    {
        if (is_bool($value)) {
            return true === $value ? '1' : '0';
        }

        if (null === $value || is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
            return (string) $value;
        }

        if (!is_array($value)) {
            throw new TypeError(sprintf('The variable '.$name.' must be NULL, a scalar or a stringable object `%s` given', gettype($value)));
        }

        if (!$isNestedListAllowed) {
            throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name);
        }

        foreach ($value as &$var) {
            $var = self::normalizeValue($var, $name, false);
        }
        unset($var);

        return $value;
    }

    /**
     * Replaces elements from passed variables into the current instance.
     */
    public function replace(VariableBag $variables): self
    {
        return new self($this->variables + $variables->variables);
    }
}
uri/LICENSE000064400000002102147361032630006350 0ustar00The MIT License (MIT)

Copyright (c) 2015 ignace nyamagana butera

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
uri-interfaces/src/Idna/Idna.php000064400000016327147361032630012530 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Idna;

use League\Uri\Exceptions\IdnaConversionFailed;
use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError;
use function defined;
use function function_exists;
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 Idna
{
    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';

    /**
     * IDNA options.
     */
    public const IDNA_DEFAULT                    = 0;
    public const IDNA_ALLOW_UNASSIGNED           = 1;
    public const IDNA_USE_STD3_RULES             = 2;
    public const IDNA_CHECK_BIDI                 = 4;
    public const IDNA_CHECK_CONTEXTJ             = 8;
    public const IDNA_NONTRANSITIONAL_TO_ASCII   = 0x10;
    public const IDNA_NONTRANSITIONAL_TO_UNICODE = 0x20;
    public const IDNA_CHECK_CONTEXTO             = 0x40;

    /**
     * IDNA errors.
     */
    public const ERROR_NONE                   = 0;
    public const ERROR_EMPTY_LABEL            = 1;
    public const ERROR_LABEL_TOO_LONG         = 2;
    public const ERROR_DOMAIN_NAME_TOO_LONG   = 4;
    public const ERROR_LEADING_HYPHEN         = 8;
    public const ERROR_TRAILING_HYPHEN        = 0x10;
    public const ERROR_HYPHEN_3_4             = 0x20;
    public const ERROR_LEADING_COMBINING_MARK = 0x40;
    public const ERROR_DISALLOWED             = 0x80;
    public const ERROR_PUNYCODE               = 0x100;
    public const ERROR_LABEL_HAS_DOT          = 0x200;
    public const ERROR_INVALID_ACE_LABEL      = 0x400;
    public const ERROR_BIDI                   = 0x800;
    public const ERROR_CONTEXTJ               = 0x1000;
    public const ERROR_CONTEXTO_PUNCTUATION   = 0x2000;
    public const ERROR_CONTEXTO_DIGITS        = 0x4000;

    /**
     * IDNA default options.
     */
    public const IDNA2008_ASCII = self::IDNA_NONTRANSITIONAL_TO_ASCII
        | self::IDNA_CHECK_BIDI
        | self::IDNA_USE_STD3_RULES
        | self::IDNA_CHECK_CONTEXTJ;
    public const IDNA2008_UNICODE = self::IDNA_NONTRANSITIONAL_TO_UNICODE
        | self::IDNA_CHECK_BIDI
        | self::IDNA_USE_STD3_RULES
        | self::IDNA_CHECK_CONTEXTJ;

    /**
     * @codeCoverageIgnore
     */
    private static function supportsIdna(): void
    {
        static $idnSupport;
        if (null === $idnSupport) {
            $idnSupport = function_exists('\idn_to_ascii') && defined('\INTL_IDNA_VARIANT_UTS46');
        }

        if (!$idnSupport) {
            throw new IdnSupportMissing('IDN host can not be processed. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.');
        }
    }

    /**
     * Converts the input to its IDNA ASCII form.
     *
     * This method returns the string converted to IDN ASCII form
     *
     * @throws SyntaxError if the string can not be converted to ASCII using IDN UTS46 algorithm
     */
    public static function toAscii(string $domain, int $options): IdnaInfo
    {
        $domain = rawurldecode($domain);

        if (1 === preg_match(self::REGEXP_IDNA_PATTERN, $domain)) {
            self::supportsIdna();

            /* @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
            idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
            if ([] === $idnaInfo) {
                return IdnaInfo::fromIntl([
                    'result' => strtolower($domain),
                    'isTransitionalDifferent' => false,
                    'errors' => self::validateDomainAndLabelLength($domain),
                ]);
            }

            /* @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
            return IdnaInfo::fromIntl($idnaInfo);
        }

        $error = self::ERROR_NONE;
        if (1 !== preg_match(self::REGEXP_REGISTERED_NAME, $domain)) {
            $error |= self::ERROR_DISALLOWED;
        }

        return IdnaInfo::fromIntl([
            'result' => strtolower($domain),
            'isTransitionalDifferent' => false,
            'errors' => self::validateDomainAndLabelLength($domain) | $error,
        ]);
    }

    /**
     * Converts the input to its IDNA UNICODE form.
     *
     * This method returns the string converted to IDN UNICODE form
     *
     * @throws SyntaxError if the string can not be converted to UNICODE using IDN UTS46 algorithm
     */
    public static function toUnicode(string $domain, int $options): IdnaInfo
    {
        $domain = rawurldecode($domain);

        if (false === stripos($domain, 'xn--')) {
            return IdnaInfo::fromIntl(['result' => $domain, 'isTransitionalDifferent' => false, 'errors' => self::ERROR_NONE]);
        }

        self::supportsIdna();

        /* @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
        idn_to_utf8($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
        if ([] === $idnaInfo) {
            throw IdnaConversionFailed::dueToInvalidHost($domain);
        }

        /* @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
        return IdnaInfo::fromIntl($idnaInfo);
    }

    /**
     * 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 = self::ERROR_NONE;
        $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 |= self::ERROR_DOMAIN_NAME_TOO_LONG;
        }

        foreach ($labels as $label) {
            if (strlen($label) > self::MAX_LABEL_LENGTH) {
                $error |= self::ERROR_LABEL_TOO_LONG;

                break;
            }
        }

        return $error;
    }
}
uri-interfaces/src/Idna/IdnaInfo.php000064400000007236147361032630013343 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Idna;

use function array_filter;
use const ARRAY_FILTER_USE_KEY;

/**
 * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
 */
final class IdnaInfo
{
    private const ERRORS = [
        Idna::ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty',
        Idna::ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes',
        Idna::ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form',
        Idna::ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")',
        Idna::ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")',
        Idna::ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions',
        Idna::ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark',
        Idna::ERROR_DISALLOWED => 'a label or domain name contains disallowed characters',
        Idna::ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode',
        Idna::ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop',
        Idna::ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string',
        Idna::ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)',
        Idna::ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements',
        Idna::ERROR_CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits',
        Idna::ERROR_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',
    ];

    /** @var string */
    private $result;

    /** @var bool */
    private $isTransitionalDifferent;

    /** @var int */
    private $errors;

    /**
     * @var array<int, string>
     */
    private $errorList;

    private function __construct(string $result, bool $isTransitionalDifferent, int $errors)
    {
        $this->result = $result;
        $this->errors = $errors;
        $this->isTransitionalDifferent = $isTransitionalDifferent;
        $this->errorList = array_filter(
            self::ERRORS,
            function (int $error): bool {
                return 0 !== ($error & $this->errors);
            },
            ARRAY_FILTER_USE_KEY
        );
    }

    /**
     * @param array{result:string, isTransitionalDifferent:bool, errors:int} $infos
     */
    public static function fromIntl(array $infos): self
    {
        return new self($infos['result'], $infos['isTransitionalDifferent'], $infos['errors']);
    }

    /**
     * @param array{result:string, isTransitionalDifferent:bool, errors:int} $properties
     */
    public static function __set_state(array $properties): self
    {
        return self::fromIntl($properties);
    }

    public function result(): string
    {
        return $this->result;
    }

    public function isTransitionalDifferent(): bool
    {
        return $this->isTransitionalDifferent;
    }

    public function errors(): int
    {
        return $this->errors;
    }

    public function error(int $error): ?string
    {
        return $this->errorList[$error] ?? null;
    }

    /**
     * @return array<int, string>
     */
    public function errorList(): array
    {
        return $this->errorList;
    }
}
uri-interfaces/src/Contracts/SegmentedPathInterface.php000064400000010142147361032630017300 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

use League\Uri\Exceptions\SyntaxError;

/**
 * @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
     *
     * @param ?string $segment
     *
     * @return int[]
     */
    public function keys(?string $segment = null): array;

    /**
     * Appends a segment to the path.
     */
    public function append(string $segment): self;

    /**
     * Prepends a segment to the path.
     */
    public function prepend(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.
     *
     * @param ?string $segment
     *
     * @throws SyntaxError If the key is invalid
     */
    public function withSegment(int $key, ?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.
     *
     * @param int ...$keys remaining keys to remove
     *
     * @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.
     *
     * @param ?string $path
     */
    public function withDirname(?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.
     *
     * @param ?string $basename
     */
    public function withBasename(?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.
     *
     * @param ?string $extension
     */
    public function withExtension(?string $extension): self;
}
uri-interfaces/src/Contracts/AuthorityInterface.php000064400000005367147361032630016555 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError;

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;

    /**
     * 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.
     *
     * @param  ?string           $host
     * @throws SyntaxError       for invalid component or transformations
     *                                that would result in a object in invalid state.
     * @throws IdnSupportMissing for component or transformations
     *                                requiring IDN support when IDN support is not present
     *                                or misconfigured.
     */
    public function withHost(?string $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.
     *
     * @param ?int $port
     *
     * @throws SyntaxError for invalid component or transformations
     *                     that would result in a 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.
     *
     * @param ?string $user
     * @param ?string $password
     *
     * @throws SyntaxError for invalid component or transformations
     *                     that would result in a object in invalid state.
     */
    public function withUserInfo(?string $user, ?string $password = null): self;
}
uri-interfaces/src/Contracts/DataPathInterface.php000064400000005230147361032630016240 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

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
     * a 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(string $parameters): self;
}
uri-interfaces/src/Contracts/DomainHostInterface.php000064400000005435147361032630016626 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

use League\Uri\Exceptions\SyntaxError;

/**
 * @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.
     *
     * @param ?string $label
     *
     * @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(string $label): self;

    /**
     * Appends a label to the host.
     */
    public function append(string $label): 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, 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.
     *
     * @param int ...$keys
     *
     * @throws SyntaxError If the key is invalid
     */
    public function withoutLabel(int ...$keys): self;
}
uri-interfaces/src/Contracts/UriException.php000064400000000557147361032630015356 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

use Throwable;

interface UriException extends Throwable
{
}
uri-interfaces/src/Contracts/PortInterface.php000064400000000726147361032630015503 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

interface PortInterface extends UriComponentInterface
{
    /**
     * Returns the integer representation of the Port.
     */
    public function toInt(): ?int;
}
uri-interfaces/src/Contracts/UserInfoInterface.php000064400000001743147361032630016311 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

interface UserInfoInterface extends UriComponentInterface
{
    /**
     * Returns the user component part.
     */
    public function getUser(): ?string;

    /**
     * Returns the pass component part.
     */
    public function getPass(): ?string;

    /**
     * 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 user.
     *
     * An empty user is equivalent to removing the user information.
     *
     * @param ?string $user
     * @param ?string $pass
     */
    public function withUserInfo(?string $user, ?string $pass = null): self;
}
uri-interfaces/src/Contracts/IpHostInterface.php000064400000002337147361032630015765 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

interface IpHostInterface extends HostInterface
{
    /**
     * Returns whether or not the host is an IPv4 address.
     */
    public function isIpv4(): bool;
    /**
     * Returns whether or not the host is an IPv6 address.
     */
    public function isIpv6(): bool;

    /**
     * Returns whether or not the host is an IPv6 address.
     */
    public function isIpFuture(): bool;

    /**
     * Returns whether or not the host has a ZoneIdentifier.
     *
     * @see http://tools.ietf.org/html/rfc6874#section-4
     */
    public function hasZoneIdentifier(): bool;

    /**
     * Returns an 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;
}
uri-interfaces/src/Contracts/QueryInterface.php000064400000016224147361032630015664 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

/**
 * @extends \IteratorAggregate<array{0:string, 1:string|null}>
 */
interface QueryInterface extends \Countable, \IteratorAggregate, UriComponentInterface
{
    /**
     * Returns the query separator.
     */
    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 a 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 pair with a specific name exists.
     *
     * @see https://url.spec.whatwg.org/#dom-urlsearchparams-has
     */
    public function has(string $key): 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.
     *
     * If a key is submitted it will returns the value attached to it or null
     *
     * @see http://php.net/parse_str
     * @see https://wiki.php.net/rfc/on_demand_name_mangling
     *
     * @param  ?string $key
     * @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 params(?string $key = null);

    /**
     * Returns the RFC1738 encoded query.
     */
    public function toRFC1738(): ?string;

    /**
     * Returns the RFC3986 encoded query.
     *
     * @see ::getContent
     */
    public function toRFC3986(): ?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;

    /**
     * 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.
     *
     * ie.: toto[3]=bar[3]&foo=bar becomes toto[]=bar[3]&foo=bar
     */
    public function withoutNumericIndices(): self;

    /**
     * Returns an instance with the 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
     *
     * @param ?string $value
     */
    public function withPair(string $key, ?string $value): 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(string $query): self;

    /**
     * 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
     *
     * @param string ...$keys
     */
    public function withoutPair(string ...$keys): 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
     *
     * @param ?string $value
     */
    public function appendTo(string $key, ?string $value): 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(string $query): 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.
     *
     * @param string ...$keys
     */
    public function withoutParam(string ...$keys): self;
}
uri-interfaces/src/Contracts/FragmentInterface.php000064400000000715147361032630016320 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

interface FragmentInterface extends UriComponentInterface
{
    /**
     * Returns the decoded fragment.
     */
    public function decoded(): ?string;
}
uri-interfaces/src/Contracts/PathInterface.php000064400000005510147361032630015447 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

use League\Uri\Exceptions\SyntaxError;

interface PathInterface extends UriComponentInterface
{
    /**
     * Returns the decoded path.
     */
    public function decoded(): string;

    /**
     * Returns whether or not the path is absolute or relative.
     */
    public function isAbsolute(): bool;

    /**
     * Returns whether or not the path has a trailing delimiter.
     */
    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;
}
uri-interfaces/src/Contracts/UriComponentInterface.php000064400000006027147361032630017201 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError;

interface UriComponentInterface extends \JsonSerializable
{
    /**
     * Returns the instance content.
     *
     * If the instance is defined, the value returned MUST be encoded according to the
     * selected encoding algorithm. In any case, the value MUST NOT double-encode any character
     * depending on the selected encoding algorithm.
     *
     * To determine what characters to encode, please refer to RFC 3986, Sections 2 and 3.
     * or RFC 3987 Section 3. By default the content is encoded according to RFC3986
     *
     * If the instance is not defined null is returned
     */
    public function getContent(): ?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;

    /**
     * Returns an instance with the specified content.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified content.
     *
     * Users can provide both encoded and decoded content characters.
     *
     * A null value is equivalent to removing the component content.
     *
     *
     * @param ?string $content
     *
     * @throws SyntaxError       for invalid component or transformations
     *                           that would result in a object in invalid state.
     * @throws IdnSupportMissing for component or transformations
     *                           requiring IDN support when IDN support is not present
     *                           or misconfigured.
     */
    public function withContent(?string $content): self;
}
uri-interfaces/src/Contracts/UriInterface.php000064400000024454147361032630015322 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Contracts;

use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError;

interface UriInterface extends \JsonSerializable
{
    /**
     * 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;

    /**
     * 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.
     *
     * @param ?string $scheme
     *
     * @throws SyntaxError for invalid component or transformations
     *                     that would result in a object in invalid state.
     */
    public function withScheme(?string $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.
     *
     * @param ?string $user
     * @param ?string $password
     *
     * @throws SyntaxError for invalid component or transformations
     *                     that would result in a object in invalid state.
     */
    public function withUserInfo(?string $user, ?string $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.
     *
     * @param ?string $host
     *
     * @throws SyntaxError       for invalid component or transformations
     *                           that would result in a object in invalid state.
     * @throws IdnSupportMissing for component or transformations
     *                           requiring IDN support when IDN support is not present
     *                           or misconfigured.
     */
    public function withHost(?string $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.
     *
     * @param ?int $port
     *
     * @throws SyntaxError for invalid component or transformations
     *                     that would result in a 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 a object in invalid state.
     */
    public function withPath(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.
     *
     * @param ?string $query
     *
     * @throws SyntaxError for invalid component or transformations
     *                     that would result in a object in invalid state.
     */
    public function withQuery(?string $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.
     *
     * @param ?string $fragment
     *
     * @throws SyntaxError for invalid component or transformations
     *                     that would result in a object in invalid state.
     */
    public function withFragment(?string $fragment): self;
}
uri-interfaces/src/Contracts/HostInterface.php000064400000002133147361032630015466 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\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;
}
uri-interfaces/src/Exceptions/IdnaConversionFailed.php000064400000002122147361032630017135 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Exceptions;

use League\Uri\Idna\IdnaInfo;

final class IdnaConversionFailed extends SyntaxError
{
    /** @var IdnaInfo|null  */
    private $idnaInfo;

    private function __construct(string $message, IdnaInfo $idnaInfo = null)
    {
        parent::__construct($message);
        $this->idnaInfo = $idnaInfo;
    }

    public static function dueToIDNAError(string $domain, IdnaInfo $idnaInfo): self
    {
        return new self(
            'The host `'.$domain.'` is invalid : '.implode(', ', $idnaInfo->errorList()).' .',
            $idnaInfo
        );
    }

    public static function dueToInvalidHost(string $domain): self
    {
        return new self('The host `'.$domain.'` is not a valid IDN host');
    }

    public function idnaInfo(): ?IdnaInfo
    {
        return $this->idnaInfo;
    }
}
uri-interfaces/src/Exceptions/IdnSupportMissing.php000064400000000651147361032630016555 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Exceptions;

use League\Uri\Contracts\UriException;

class IdnSupportMissing extends \RuntimeException implements UriException
{
}
uri-interfaces/src/Exceptions/SyntaxError.php000064400000000653147361032630015416 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Exceptions;

use League\Uri\Contracts\UriException;

class SyntaxError extends \InvalidArgumentException implements UriException
{
}
uri-interfaces/src/Exceptions/FileinfoSupportMissing.php000064400000000656147361032630017603 0ustar00<?php

/**
 * League.Uri (https://uri.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace League\Uri\Exceptions;

use League\Uri\Contracts\UriException;

class FileinfoSupportMissing extends \RuntimeException implements UriException
{
}
uri-interfaces/LICENSE000064400000002102147361032630010471 0ustar00The MIT License (MIT)

Copyright (c) 2016 ignace nyamagana butera

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
climate/CONTRIBUTING.md000064400000002467147361032630010431 0ustar00# Contributing

Contributions are **welcome** and will be fully **credited**.

We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/climate).


## Pull Requests

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.

- **Create feature branches** - Don't ask us to pull from your master branch.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.


## Running Tests

``` bash
$ composer test
```


**Happy coding**!
climate/Gulpfile.js000064400000000127147361032630010274 0ustar00var elixir = require('laravel-elixir');

elixir(function(mix) {
    mix.phpUnit();
});
climate/CONDUCT.md000064400000003675147361032630007623 0ustar00# Contributor Code of Conduct

As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.

This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
climate/CODE_OF_CONDUCT.md000064400000003675147361032630011001 0ustar00# Contributor Code of Conduct

As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.

This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
climate/src/Util/UtilImporter.php000064400000000557147361032630013052 0ustar00<?php

namespace League\CLImate\Util;

trait UtilImporter
{
    /**
     * An instance of the UtilFactory
     *
     * @var \League\CLImate\Util\UtilFactory $util
     */
    protected $util;

    /**
     * Sets the $util property
     *
     * @param UtilFactory $util
     */
    public function util(UtilFactory $util)
    {
        $this->util = $util;
    }
}
climate/src/Util/Cursor.php000064400000003120147361032630011655 0ustar00<?php

namespace League\CLImate\Util;

class Cursor
{
    /**
     * Move the cursor up in the terminal x number of lines.
     *
     * @param int $lines
     *
     * @return string
     */
    public function up($lines = 1)
    {
        return "\e[{$lines}A";
    }

    /**
     * Move the cursor down in the terminal x number of lines.
     *
     * @param int $lines
     *
     * @return string
     */
    public function down($lines = 1)
    {
        return "\e[{$lines}B";
    }

    /**
     * Move the cursor right in the terminal x number of columns.
     *
     * @param int $cols
     *
     * @return string
     */
    public function right($columns = 1)
    {
        return "\e[{$columns}C";
    }

    /**
     * Move the cursor left in the terminal x number of columns.
     *
     * @param int $cols
     *
     * @return string
     */
    public function left($cols = 1)
    {
        return "\e[{$cols}D";
    }

    /**
     * Move cursor to the beginning of the current line.
     *
     * @return string
     */
    public function startOfCurrentLine()
    {
        return "\r";
    }

    /**
     * Delete the current line to the end.
     *
     * @return string
     */
    public function deleteCurrentLine()
    {
        return "\e[K";
    }

    /**
     * Get the style for hiding the cursor
     *
     * @return string
     */
    public function hide()
    {
        return "\e[?25l";
    }

    /**
     * Get the style for returning the cursor to its default
     *
     * @return string
     */
    public function defaultStyle()
    {
        return "\e[?25h";
    }
}
climate/src/Util/OutputImporter.php000064400000000564147361032630013433 0ustar00<?php

namespace League\CLImate\Util;

trait OutputImporter
{
    /**
     * An instance of the OutputFactory
     *
     * @var \League\CLImate\Util\Output $output
     */
    protected $output;

    /**
     * Sets the $output property
     *
     * @param Output $output
     */
    public function output(Output $output)
    {
        $this->output = $output;
    }
}
climate/src/Util/Helper.php000064400000001536147361032630011630 0ustar00<?php

namespace League\CLImate\Util;

class Helper
{
    /**
     * @param string|array $var
     *
     * @return array
     */
    public static function toArray($var)
    {
        if (!is_array($var)) {
            return [$var];
        }

        return $var;
    }

    /**
     * Flatten a multi-dimensional array
     *
     * @param array $arr
     *
     * @return array
     */
    public static function flatten(array $arr)
    {
        $flattened = [];

        array_walk_recursive($arr, function ($a) use (&$flattened) {
            $flattened[] = $a;
        });

        return $flattened;
    }

    /**
     * Convert a string to snake case
     *
     * @param string $str
     *
     * @return string
     */
    public static function snakeCase($str)
    {
        return strtolower(preg_replace('/(.)([A-Z])/', '$1_$2', $str));
    }
}
climate/src/Util/Reader/Stdin.php000064400000003550147361032630012672 0ustar00<?php

namespace League\CLImate\Util\Reader;

use League\CLImate\Exceptions\RuntimeException;
use Seld\CliPrompt\CliPrompt;

class Stdin implements ReaderInterface
{
    protected $stdIn = false;

    /**
     * Read the line typed in by the user
     *
     * @return string
     */
    public function line()
    {
        return trim(fgets($this->getStdIn(), 1024));
    }

    /**
     * Read from STDIN until EOF (^D) is reached
     *
     * @return string
     */
    public function multiLine()
    {
        return trim(stream_get_contents($this->getStdIn()));
    }

    /**
     * Read one character
     *
     * @param int $count
     *
     * @return string
     */
    public function char($count = 1)
    {
        return fread($this->getStdIn(), $count);
    }

    /**
     * Read the line, but hide what the user is typing
     *
     * @return string
     */
    public function hidden()
    {
        return CliPrompt::hiddenPrompt();
    }

    /**
     * Return a valid STDIN, even if it previously EOF'ed
     *
     * Lazily re-opens STDIN after hitting an EOF
     *
     * @return resource
     * @throws RuntimeException
     */
    protected function getStdIn()
    {
        if ($this->stdIn && !feof($this->stdIn)) {
            return $this->stdIn;
        }

        try {
            $this->setStdIn();
        } catch (\Error $e) {
            throw new RuntimeException('Unable to read from STDIN', 0, $e);
        }

        return $this->stdIn;
    }

    /**
     * Attempt to set the stdin property
     *
     * @return void
     * @throws RuntimeException
     */
    protected function setStdIn()
    {
        if ($this->stdIn !== false) {
            fclose($this->stdIn);
        }

        $this->stdIn = fopen('php://stdin', 'r');

        if (!$this->stdIn) {
            throw new RuntimeException('Unable to read from STDIN');
        }
    }
}
climate/src/Util/Reader/ReaderInterface.php000064400000000326147361032630014632 0ustar00<?php

namespace League\CLImate\Util\Reader;

interface ReaderInterface
{
    /**
     * @return string
     */
    public function line();

    /**
     * @return string
     */
    public function multiLine();
}
climate/src/Util/System/Windows.php000064400000003407147361032630013326 0ustar00<?php

namespace League\CLImate\Util\System;

class Windows extends System
{
    /**
     * Get the width of the terminal
     *
     * @return integer|null
     */
    public function width()
    {
        return $this->getDimension('width');
    }

    /**
     * Get the height of the terminal
     *
     * @return integer|null
     */
    public function height()
    {
        return $this->getDimension('height');
    }

    /**
     * Get specified terminal dimension
     *
     * @param string $key
     *
     * @return integer|null
     */

    protected function getDimension($key)
    {
        $index      = array_search($key, ['height', 'width']);
        $dimensions = $this->getDimensions();

        return (!empty($dimensions[$index])) ? $dimensions[$index] : null;
    }

    /**
     * Get information about the dimensions of the terminal
     *
     * @return array
     */
    protected function getDimensions()
    {
        $output = $this->exec('mode CON', true);

        if (!is_array($output)) {
            return [];
        }

        $output = implode("\n", $output);

        preg_match_all('/.*:\s*(\d+)/', $output, $matches);

        return (!empty($matches[1])) ? $matches[1] : [];
    }

    /**
     * Check if the stream supports ansi escape characters.
     *
     * Based on https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Output/StreamOutput.php
     *
     * @return bool
     */
    protected function systemHasAnsiSupport()
    {
        return (function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT))
            || false !== getenv('ANSICON')
            || 'ON' === getenv('ConEmuANSI')
            || 'Hyper' === getenv('TERM_PROGRAM') 
            || 'xterm' === getenv('TERM');
    }
}
climate/src/Util/System/SystemFactory.php000064400000001513147361032630014504 0ustar00<?php

namespace League\CLImate\Util\System;

class SystemFactory
{
    /**
     * @var \League\CLImate\Util\System\System $instance
     */

    protected static $instance;

    /**
     * Get an instance of the appropriate System class
     *
     * @return \League\CLImate\Util\System\System
     */

    public static function getInstance()
    {
        if (static::$instance) {
            return static::$instance;
        }

        static::$instance = self::getSystem();

        return static::$instance;
    }

    /**
     * Set the $instance property to the appropriate system
     *
     * @return \League\CLImate\Util\System\System
     */

    protected static function getSystem()
    {
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            return new Windows();
        }

        return new Linux();
    }
}
climate/src/Util/System/System.php000064400000002366147361032630013163 0ustar00<?php

namespace League\CLImate\Util\System;

abstract class System
{
    protected $force_ansi;

    /**
     * Force ansi on or off
     *
     * @param bool $force
     */
    public function forceAnsi($force = true)
    {
        $this->force_ansi = $force;
    }

    /**
     * @return integer|null
     */
    abstract public function width();

    /**
     * @return integer|null
     */
    abstract public function height();

    /**
     * Check if the stream supports ansi escape characters.
     *
     * @return bool
     */
    abstract protected function systemHasAnsiSupport();

    /**
     * Check if we are forcing ansi, fallback to system support
     *
     * @return bool
     */
    public function hasAnsiSupport()
    {
        if (is_bool($this->force_ansi)) {
            return $this->force_ansi;
        }

        return $this->systemHasAnsiSupport();
    }

    /**
     * Wraps exec function, allowing the dimension methods to decouple
     *
     * @param string $command
     * @param boolean $full
     *
     * @return string|array
     */
    public function exec($command, $full = false)
    {
        if ($full) {
            exec($command, $output);

            return $output;
        }

        return exec($command);
    }
}
climate/src/Util/System/Linux.php000064400000004457147361032630013001 0ustar00<?php

namespace League\CLImate\Util\System;

use function getenv;

class Linux extends System
{
    /**
     * Get the width of the terminal
     *
     * @return integer|null
     */
    public function width()
    {
        return $this->getDimension($this->tput("cols"));
    }

    /**
     * Get the height of the terminal
     *
     * @return integer|null
     */
    public function height()
    {
        return $this->getDimension($this->tput("lines"));
    }

    /**
     * Get a value from the tput command.
     *
     * @param string $type
     *
     * @return array|null|string
     */
    private function tput($type)
    {
        return $this->exec("tput {$type} 2>/dev/null");
    }

    /**
     * Determine if system has access to bash commands
     *
     * @return bool
     */
    public function canAccessBash()
    {
        return (rtrim($this->exec("/usr/bin/env bash -c 'echo OK'")) === 'OK');
    }

    /**
     * Display a hidden response prompt and return the response
     *
     * @param string $prompt
     *
     * @return string
     */
    public function hiddenResponsePrompt($prompt)
    {
        $bash_command = 'read -s -p "' . $prompt . '" response && echo $response';

        return rtrim($this->exec("/usr/bin/env bash -c '{$bash_command}'"));
    }

    /**
     * Determine if dimension is numeric and return it
     *
     * @param integer|string|null $dimension
     *
     * @return integer|null
     */
    protected function getDimension($dimension)
    {
        return (is_numeric($dimension)) ? $dimension : null;
    }

    /**
     * Check if the stream supports ansi escape characters.
     *
     * Based on https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Output/StreamOutput.php
     *
     * @return bool
     */
    protected function systemHasAnsiSupport()
    {
        if ('Hyper' === getenv('TERM_PROGRAM')) {
            return true;
        }
        
        $stream = STDOUT;
        
        if (function_exists('stream_isatty')) {
            return @stream_isatty($stream);
        }

        if (function_exists('posix_isatty')) {
            return @posix_isatty($stream);
        }

        $stat = @fstat($stream);
        // Check if formatted mode is S_IFCHR
        return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
    }
}
climate/src/Util/Writer/WriterInterface.php000064400000000273147361032630014757 0ustar00<?php

namespace League\CLImate\Util\Writer;

interface WriterInterface
{
    /**
     * @param  string $content
     *
     * @return void
     */
    public function write($content);
}
climate/src/Util/Writer/Buffer.php000064400000001274147361032630013075 0ustar00<?php

namespace League\CLImate\Util\Writer;

class Buffer implements WriterInterface
{
    /**
     * @var string $contents The buffered data.
     */
    protected $contents = "";

    /**
     * Write the content to the buffer.
     *
     * @param string $content
     *
     * @return void
     */
    public function write($content)
    {
        $this->contents .= $content;
    }


    /**
     * Get the buffered data.
     *
     * @return string
     */
    public function get()
    {
        return $this->contents;
    }

    /**
     * Clean the buffer and throw away any data.
     *
     * @return void
     */
    public function clean()
    {
        $this->contents = "";
    }
}
climate/src/Util/Writer/StdErr.php000064400000000412147361032630013060 0ustar00<?php

namespace League\CLImate\Util\Writer;

class StdErr implements WriterInterface
{
    /**
     * Write the content to the stream
     *
     * @param  string $content
     */
    public function write($content)
    {
        fwrite(\STDERR, $content);
    }
}
climate/src/Util/Writer/StdOut.php000064400000000412147361032630013077 0ustar00<?php

namespace League\CLImate\Util\Writer;

class StdOut implements WriterInterface
{
    /**
     * Write the content to the stream
     *
     * @param  string $content
     */
    public function write($content)
    {
        fwrite(\STDOUT, $content);
    }
}
climate/src/Util/Writer/File.php000064400000004171147361032630012542 0ustar00<?php

namespace League\CLImate\Util\Writer;

use League\CLImate\Exceptions\RuntimeException;

class File implements WriterInterface
{
    /** @var resource|string */
    protected $resource;

    /** @var boolean $close_locally */
    protected $close_locally = false;

    /** @var boolean $use_locking */
    protected $use_locking = false;

    /** @var boolean $gzip_file */
    protected $gzip_file = false;

    /**
     * @param string|resource $resource
     * @param bool $use_locking
     * @param bool $gzip_file
     */
    public function __construct($resource, $use_locking = false, $gzip_file = false)
    {
        $this->resource    = $resource;
        $this->use_locking = $use_locking;
        $this->gzip_file   = $gzip_file;
    }

    public function lock()
    {
        $this->use_locking = true;

        return $this;
    }

    public function gzipped()
    {
        $this->gzip_file = true;

        return $this;
    }

    /**
     * Write the content to the stream
     *
     * @param  string $content
     */
    public function write($content)
    {
        $resource = $this->getResource();

        if ($this->use_locking) {
            flock($resource, LOCK_EX);
        }

        gzwrite($resource, $content);

        if ($this->use_locking) {
            flock($resource, LOCK_UN);
        }
    }

    protected function getResource()
    {
        if (is_resource($this->resource)) {
            return $this->resource;
        }

        $this->close_locally = true;

        if (!is_writable($this->resource)) {
            throw new RuntimeException("The resource [{$this->resource}] is not writable");
        }

        if (!($this->resource = $this->openResource())) {
            throw new RuntimeException("The resource could not be opened");
        }

        return $this->resource;
    }

    protected function openResource()
    {
        if ($this->gzip_file) {
            return gzopen($this->resource, 'a');
        }

        return fopen($this->resource, 'a');
    }

    public function _destruct()
    {
        if ($this->close_locally) {
            gzclose($this->getResource());
        }
    }
}
climate/src/Util/UtilFactory.php000064400000002472147361032630012656 0ustar00<?php

namespace League\CLImate\Util;

use League\CLImate\Util\System\SystemFactory;
use League\CLImate\Util\System\System;

class UtilFactory
{
    /**
     * A instance of the appropriate System class
     *
     * @var \League\CLImate\Util\System\System
     */

    public $system;

    /**
     * A instance of the Cursor class
     *
     * @var \League\CLImate\Util\Cursor
     */
    public $cursor;

    public function __construct(System $system = null, Cursor $cursor = null)
    {
        $this->system = $system ?: SystemFactory::getInstance();
        $this->cursor = $cursor ?: new Cursor();
    }

    /**
     * Get the width of the terminal
     *
     * @return integer
     */

    public function width()
    {
        return (int) $this->getDimension($this->system->width(), 80);
    }

    /**
     * Get the height of the terminal
     *
     * @return integer
     */

    public function height()
    {
        return (int) $this->getDimension($this->system->height(), 25);
    }

    /**
     * Determine if the value is numeric, fallback to a default if not
     *
     * @param integer|null $dimension
     * @param integer $default
     *
     * @return integer
     */

    protected function getDimension($dimension, $default)
    {
        return (is_numeric($dimension)) ? $dimension : $default;
    }
}
climate/src/Util/Output.php000064400000015501147361032630011706 0ustar00<?php

namespace League\CLImate\Util;

use League\CLImate\Exceptions\InvalidArgumentException;
use League\CLImate\Exceptions\UnexpectedValueException;
use League\CLImate\Util\Writer\WriterInterface;

class Output
{
    /**
     * The content to be output
     *
     * @var string $content
     */
    protected $content;

    /**
     * Whether or not to add a new line after the output
     *
     * @var boolean $new_line
     */
    protected $new_line = true;

    /**
     * The array of available writers
     *
     * @var array[] $writers
     */
    protected $writers = [];

    /**
     * Default writers when one isn't specifed
     *
     * @var WriterInterface[] $default
     */
    protected $default = [];

    /**
     * Writers to be used just once
     *
     * @var null|array $once
     */
    protected $once;

    protected $persist = false;

    public function __construct()
    {
        $this->add('out', new Writer\StdOut);
        $this->add('error', new Writer\StdErr);
        $this->add('buffer', new Writer\Buffer);

        $this->defaultTo('out');
    }

    /**
     * Dictate that a new line should not be added after the output
     */
    public function sameLine()
    {
        $this->new_line = false;

        return $this;
    }

    /**
     * Add a writer to the available writers
     *
     * @param string $key
     * @param WriterInterface|array $writer
     *
     * @return \League\CLImate\Util\Output
     */
    public function add($key, $writer)
    {
        $this->writers[$key] = $this->resolve(Helper::toArray($writer));

        return $this;
    }

    /**
     * Set the default writer
     *
     * @param string|array $keys
     */
    public function defaultTo($keys)
    {
        $this->default = $this->getWriters($keys);
    }

    /**
     * Add a default writer
     *
     * @param string|array $keys
     */
    public function addDefault($keys)
    {
        $this->default = array_merge($this->default, $this->getWriters($keys));
    }

    /**
     * Register a writer to be used just once
     *
     * @param string|array $keys
     *
     * @return \League\CLImate\Util\Output
     */
    public function once($keys)
    {
        $this->once = $this->getWriters($keys);

        return $this;
    }

    /**
     * Persist or un-persist one time writers (for multi-line output)
     *
     * @param bool $persist
     *
     * @return \League\CLImate\Util\Output
     */
    public function persist($persist = true)
    {
        $this->persist = (bool) $persist;

        if (!$this->persist) {
            $this->resetOneTimers();
        }

        return $this;
    }

    /**
     * Get a specific writer
     *
     * @param string $writer
     *
     * @return WriterInterface|array
     * @throws UnexpectedValueException if writer key doesn't exist
     */
    public function get($writer)
    {
        if (!array_key_exists($writer, $this->writers)) {
            throw new UnexpectedValueException('Unknown writer [' . $writer . ']');
        }

        if (count($this->writers[$writer]) == 1) {
            return reset($this->writers[$writer]);
        }

        return $this->writers[$writer];
    }

    /**
     * Get the currently available writers
     *
     * @return array
     */
    public function getAvailable()
    {
        $writers = [];

        foreach ($this->writers as $key => $writer) {
            $writers[$key] = $this->getReadable($writer);
        }

        return $writers;
    }

    /**
     * Write the content using the provided writer
     *
     * @param  string $content
     */
    public function write($content)
    {
        if ($this->new_line) {
            $content .= PHP_EOL;
        }

        foreach ($this->getCurrentWriters() as $writer) {
            $writer->write($content);
        }

        $this->resetOneTimers();
    }

    /**
     * Resolve the writer(s) down to an array of WriterInterface classes
     *
     * @param WriterInterface|array|string $writer
     *
     * @return array
     */
    protected function resolve($writer)
    {
        $resolver = 'resolve' . ucwords(gettype($writer)) . 'Writer';

        if (method_exists($this, $resolver) && $resolved = $this->{$resolver}($writer)) {
            return $resolved;
        }

        $this->handleUnknownWriter($writer);
    }

    /**
     * @param array $writer
     *
     * @return array
     */
    protected function resolveArrayWriter($writer)
    {
        return Helper::flatten(array_map([$this, 'resolve'], $writer));
    }

    /**
     * @param object $writer
     *
     * @return WriterInterface|false
     */
    protected function resolveObjectWriter($writer)
    {
        if ($writer instanceof WriterInterface) {
            return $writer;
        }

        return false;
    }

    /**
     * @param string $writer
     *
     * @return array|false
     */
    protected function resolveStringWriter($writer)
    {
        if (is_string($writer) && array_key_exists($writer, $this->writers)) {
            return $this->writers[$writer];
        }

        return false;
    }

    /**
     * @param mixed $writer
     * @throws InvalidArgumentException For non-valid writer
     */
    protected function handleUnknownWriter($writer)
    {
        // If we've gotten this far and don't know what it is,
        // let's at least try and give a helpful error message
        if (is_object($writer)) {
            throw new InvalidArgumentException('Class [' . get_class($writer) . '] must implement '
                                    . 'League\CLImate\Util\Writer\WriterInterface.');
        }

        // No idea, just tell them we can't resolve it
        throw new InvalidArgumentException('Unable to resolve writer [' . $writer . ']');
    }

    /**
     * Get the readable version of the writer(s)
     *
     * @param array $writer
     *
     * @return string|array
     */
    protected function getReadable(array $writer)
    {
        $classes = array_map('get_class', $writer);

        if (count($classes) == 1) {
            return reset($classes);
        }

        return $classes;
    }

    /**
     * Get the writers based on their keys
     *
     * @param string|array $keys
     *
     * @return array
     */
    protected function getWriters($keys)
    {
        $writers = array_flip(Helper::toArray($keys));

        return Helper::flatten(array_intersect_key($this->writers, $writers));
    }

    /**
     * @return WriterInterface[]
     */
    protected function getCurrentWriters()
    {
        return $this->once ?: $this->default;
    }

    /**
     * Reset anything only used for the current content being written
     */
    protected function resetOneTimers()
    {
        // Reset new line flag for next time
        $this->new_line = true;

        if (!$this->persist) {
            // Reset once since we only want to use it... once.
            $this->once = null;
        }
    }
}
climate/src/Argument/Argument.php000064400000020611147361032630013033 0ustar00<?php

namespace League\CLImate\Argument;

use League\CLImate\Exceptions\UnexpectedValueException;
use function is_array;

class Argument
{
    /**
     * An argument's name.
     *
     * Use this name when internally referring to the argument.
     *
     * @var string
     */
    protected $name;

    /**
     * An argument's short representation.
     *
     * @var string
     */
    protected $prefix;

    /**
     * An argument's long representation.
     *
     * @var string
     */
    protected $longPrefix;

    /**
     * An argument's description.
     *
     * @var string
     */
    protected $description;

    /**
     * Whether or not an argument is required.
     *
     * @var bool
     */
    protected $required = false;

    /**
     * Whether or not an argument only needs to be defined to have a value.
     *
     * These arguments have the value true when they are defined on the command
     * line.
     *
     * @var bool
     */
    protected $noValue = false;

    /**
     * Which data type to cast an argument's value to.
     *
     * Valid data types are "string", "int", "float", and "bool".
     *
     * @var string
     */
    protected $castTo = 'string';

    /**
     * An argument's default value.
     *
     * @var string
     */
    protected $defaultValue = [];

    /**
     * An argument's value, after type casting.
     *
     * @var string[]|int[]|float[]|bool[]
     */
    protected $values = [];

    /**
     * Build a new command argument.
     *
     * @param string $name
     */
    public function __construct($name)
    {
        $this->setName($name);
    }

    /**
     * Build a new command argument from an array.
     *
     * @param string $name
     * @param array $params
     *
     * @return Argument
     */
    public static function createFromArray($name, array $params)
    {
        $argument = new Argument($name);
        $params   = self::getSettableArgumentParams($params);

        foreach ($params as $key => $value) {
            $method = 'set' . ucwords($key);
            $argument->{$method}($value);
        }

        return $argument;
    }

    /**
     * Get argument params based on settable properties
     *
     * @param array $params
     *
     * @return array
     */
    protected static function getSettableArgumentParams(array $params)
    {
        $allowed = [
                    'prefix',
                    'longPrefix',
                    'description',
                    'required',
                    'noValue',
                    'castTo',
                    'defaultValue',
                ];

        return array_intersect_key($params, array_flip($allowed));
    }

    /**
     * Retrieve an argument's name.
     *
     * Use this name when internally referring to the argument.
     *
     * @return string
     */
    public function name()
    {
        return $this->name;
    }

    /**
     * Set an argument's name.
     *
     * Use this name when internally referring to the argument.
     *
     * @param string $name
     */
    protected function setName($name)
    {
        $this->name = trim($name);
    }

    /**
     * Retrieve an argument's short form.
     *
     * @return string
     */
    public function prefix()
    {
        return $this->prefix;
    }

    /**
     * Set an argument's short form.
     *
     * @param string $prefix
     */
    protected function setPrefix($prefix)
    {
        $this->prefix = trim($prefix);
    }

    /**
     * Retrieve an argument's long form.
     *
     * @return string
     */
    public function longPrefix()
    {
        return $this->longPrefix;
    }

    /**
     * Set an argument's short form.
     *
     * @param string $longPrefix
     */
    protected function setLongPrefix($longPrefix)
    {
        $this->longPrefix = trim($longPrefix);
    }

    /**
     * Determine if an argument has a prefix.
     *
     * @return bool
     */
    public function hasPrefix()
    {
        return $this->prefix() || $this->longPrefix();
    }

    /**
     * Retrieve an argument's description.
     *
     * @return string
     */
    public function description()
    {
        return $this->description;
    }

    /**
     * Set an argument's description.
     *
     * @param string $description
     */
    protected function setDescription($description)
    {
        $this->description = trim($description);
    }

    /**
     * Determine whether or not an argument is required.
     *
     * @return bool
     */
    public function isRequired()
    {
        return $this->required;
    }

    /**
     * Set whether an argument is required or not.
     *
     * @param bool $required
     */
    protected function setRequired($required)
    {
        $this->required = (bool) $required;
    }

    /**
     * Determine whether or not an argument only needs to be defined to have a
     * value.
     *
     * @return bool
     */
    public function noValue()
    {
        return $this->noValue;
    }

    /**
     * Set whether or not an argument only needs to be defined to have a value.
     *
     * @param bool $noValue
     */
    protected function setNoValue($noValue)
    {
        $this->setCastTo('bool');
        $this->noValue = (bool) $noValue;
    }

    /**
     * Retrieve the data type to cast an argument's value to.
     *
     * @return string
     */
    public function castTo()
    {
        return $this->castTo;
    }

    /**
     * Set the data type to cast an argument's value to.
     *
     * Valid data types are "string", "int", "float", and "bool".
     *
     * @param string $castTo
     *
     * @return void
     * @throws UnexpectedValueException if $castTo is not a valid data type.
     */
    protected function setCastTo($castTo)
    {
        if (!in_array($castTo, ['string', 'int', 'float', 'bool'])) {
            throw new UnexpectedValueException(
                "An argument may only be cast to the data type "
                . "'string', 'int', 'float', or 'bool'."
            );
        }

        $this->castTo = $this->noValue() ? 'bool' : $castTo;
    }

    /**
     * Retrieve an argument's default values.
     *
     * @return string
     */
    public function defaultValue()
    {
        return $this->defaultValue;
    }

    /**
     * Set an argument's default value.
     *
     * @param string $defaultValue
     */
    public function setDefaultValue($defaultValue)
    {
        if (!is_array($defaultValue)) {
            $defaultValue = [$defaultValue];
        }
        $this->defaultValue = $defaultValue;
    }

    /**
     * Retrieve an argument's value.
     *
     * Argument values are type cast based on the value of $castTo.
     *
     * @return string|int|float|bool
     */
    public function value()
    {
        if ($this->values) {
            return end($this->values);
        }
        $cast_method = 'castTo' . ucwords($this->castTo);
        return $this->{$cast_method}(current($this->defaultValue()));
    }

    /**
     * Retrieve an argument's values.
     *
     * Argument values are type cast based on the value of $castTo.
     *
     * @return string[]|int[]|float[]|bool[]
     */
    public function values()
    {
        if ($this->values) {
            return $this->values;
        }
        $cast_method = 'castTo' . ucwords($this->castTo);
        return array_map([$this, $cast_method], $this->defaultValue());
    }

    /**
     * @deprecated use values() instead.
     */
    public function valueArray()
    {
        return $this->values();
    }

    /**
     * Set an argument's value based on its command line entry.
     *
     * Argument values are type cast based on the value of $castTo.
     *
     * @param string|bool $value
     */
    public function setValue($value)
    {
        $cast_method = 'castTo' . ucwords($this->castTo);
        $this->values[] = $this->{$cast_method}($value);
    }

    /**
     * @param string $value
     *
     * @return string
     */
    protected function castToString($value)
    {
        return (string) $value;
    }

    /**
     * @param string $value
     *
     * @return int
     */
    protected function castToInt($value)
    {
        return (int) $value;
    }

    /**
     * @param string $value
     *
     * @return float
     */
    protected function castToFloat($value)
    {
        return (float) $value;
    }

    /**
     * @param string $value
     *
     * @return bool
     */
    protected function castToBool($value)
    {
        return (bool) $value;
    }
}
climate/src/Argument/Parser.php000064400000017251147361032630012513 0ustar00<?php

namespace League\CLImate\Argument;

use League\CLImate\Exceptions\InvalidArgumentException;

class Parser
{
    /**
     * Filter class to find various types of arguments
     *
     * @var \League\CLImate\Argument\Filter $filter
     */
    protected $filter;

    /**
     * Summary builder class
     *
     * @var \League\CLImate\Argument\Summary $summary
     */
    protected $summary;

    protected $trailing;

    public function __construct()
    {
        $this->summary = new Summary();
    }

    /**
     * @param Filter $filter
     * @param Argument[] $arguments
     *
     * @return \League\CLImate\Argument\Parser
     */
    public function setFilter($filter, $arguments)
    {
        $this->filter = $filter;
        $this->filter->setArguments($arguments);

        return $this;
    }

    /**
     * Parse command line arguments into CLImate arguments.
     *
     * @param array $argv
     *
     * @return void
     * @throws InvalidArgumentException if required arguments aren't defined.
     */
    public function parse(array $argv = null)
    {
        $cliArguments = $this->arguments($argv);

        if (in_array('--', $cliArguments)) {
            $cliArguments = $this->removeTrailingArguments($cliArguments);
        }

        $unParsedArguments = $this->prefixedArguments($cliArguments);

        $this->nonPrefixedArguments($unParsedArguments);

        // After parsing find out which arguments were required but not
        // defined on the command line.
        $missingArguments = $this->filter->missing();

        if (count($missingArguments) > 0) {
            throw new InvalidArgumentException(
                'The following arguments are required: '
                . $this->summary->short($missingArguments) . '.'
            );
        }
    }

    /**
     * Get the command name.
     *
     * @param array $argv
     *
     * @return string
     */
    public function command(array $argv = null)
    {
        return $this->getCommandAndArguments($argv)['command'];
    }

    /**
     * Get the passed arguments.
     *
     * @param array $argv
     *
     * @return array
     */
    public function arguments(array $argv = null)
    {
        return $this->getCommandAndArguments($argv)['arguments'];
    }

    /**
     * Get the trailing arguments
     *
     * @return string|null
     */
    public function trailing()
    {
        return $this->trailing;
    }

    /**
     * Remove the trailing arguments from the parser and set them aside
     *
     * @param array $arguments
     *
     * @return array
     */
    protected function removeTrailingArguments(array $arguments)
    {
        $trailing = array_splice($arguments, array_search('--', $arguments));
        array_shift($trailing);
        $this->trailing = implode(' ', $trailing);

        return $arguments;
    }

    /**
     * Parse command line options into prefixed CLImate arguments.
     *
     * Prefixed arguments are arguments with a prefix (-) or a long prefix (--)
     * on the command line.
     *
     * Return the arguments passed on the command line that didn't match up with
     * prefixed arguments so they can be assigned to non-prefixed arguments.
     *
     * @param array $argv
     * @return array
     */
    protected function prefixedArguments(array $argv = [])
    {
        foreach ($argv as $key => $passed_argument) {
            $argv = $this->trySettingArgumentValue($argv, $key, $passed_argument);
        }

        // Send un-parsed arguments back upstream.
        return array_values($argv);
    }

    /**
     * Parse unset command line options into non-prefixed CLImate arguments.
     *
     * Non-prefixed arguments are parsed after the prefixed arguments on the
     * command line, in the order that they're defined in the script.
     *
     * @param array $unParsedArguments
     */
    protected function nonPrefixedArguments(array $unParsedArguments = [])
    {
        foreach ($this->filter->withoutPrefix() as $key => $argument) {
            if (isset($unParsedArguments[$key])) {
                $argument->setValue($unParsedArguments[$key]);
            }
        }
    }

    /**
     * Parse the name and value of the argument passed in
     *
     * @param string $cliArgument
     * @return string[] [$name, $value]
     */
    protected function getNameAndValue($cliArgument)
    {
        // Look for arguments defined in the "key=value" format.
        if (strpos($cliArgument, '=') !== false) {
            return explode('=', $cliArgument, 2);
        }

        // If the argument isn't in "key=value" format then assume it's in
        // "key value" format and define the value after we've found the
        // matching CLImate argument.
        return [$cliArgument, null];
    }

    /**
     * Attempt to set the an argument's value and remove applicable
     * arguments from array
     *
     * @param array $argv
     * @param int $key
     * @param string $passed_argument
     *
     * @return array The new $argv
     */
    protected function trySettingArgumentValue($argv, $key, $passed_argument)
    {
        list($name, $value) = $this->getNameAndValue($passed_argument);

        // Look for the argument in our defined $arguments
        // and assign their value.
        if (!($argument = $this->findPrefixedArgument($name))) {
            return $argv;
        }

        // We found an argument key, so take it out of the array.
        unset($argv[$key]);

        return $this->setArgumentValue($argv, $argument, $key, $value);
    }

    /**
     * Set the argument's value
     *
     * @param array $argv
     * @param Argument $argument
     * @param int $key
     * @param string|null $value
     *
     * @return array The new $argv
     */
    protected function setArgumentValue($argv, $argument, $key, $value)
    {
        // Arguments are given the value true if they only need to
        // be defined on the command line to be set.
        if ($argument->noValue()) {
            $argument->setValue(true);
            return $argv;
        }

        if (is_null($value)) {
            if (count($argv) === 0) {
                return $argv;
            }

            // If the value wasn't previously defined in "key=value"
            // format then define it from the next command argument.
            $nextArgvValue = $argv[$key + 1];
            if ($this->isValidArgumentValue($nextArgvValue)) {
                $argument->setValue($nextArgvValue);
                unset($argv[$key + 1]);
                return $argv;
            }
        }

        $argument->setValue($value);

        return $argv;
    }

    /**
     * Check if the value is considered a valid input value.
     *
     * @param $argumentValue
     * @return bool
     */
    protected function isValidArgumentValue($argumentValue)
    {
        return empty($this->findPrefixedArgument($argumentValue));
    }

    /**
     * Search for argument in defined prefix arguments
     *
     * @param string $name
     *
     * @return Argument|false
     */
    protected function findPrefixedArgument($name)
    {
        foreach ($this->filter->withPrefix() as $argument) {
            if (in_array($name, ["-{$argument->prefix()}", "--{$argument->longPrefix()}"])) {
                return $argument;
            }
        }

        return false;
    }

    /**
     * Pull a command name and arguments from $argv.
     *
     * @param array $argv
     * @return array
     */
    protected function getCommandAndArguments(array $argv = null)
    {
        // If no $argv is provided then use the global PHP defined $argv.
        if (is_null($argv)) {
            global $argv;
        }

        $arguments = $argv;
        $command   = array_shift($arguments);

        return compact('arguments', 'command');
    }
}
climate/src/Argument/Filter.php000064400000007167147361032630012511 0ustar00<?php

namespace League\CLImate\Argument;

class Filter
{
    protected $arguments = [];

    /**
     * Set the available arguments
     *
     * @param array $arguments
     */
    public function setArguments(array $arguments)
    {
        $this->arguments = $arguments;
    }

    /**
     * Retrieve optional arguments
     *
     * @return Argument[]
     */
    public function optional()
    {
        return $this->filterArguments(['isOptional']);
    }

    /**
     * Retrieve required arguments
     *
     * @return Argument[]
     */
    public function required()
    {
        return $this->filterArguments(['isRequired']);
    }

    /**
     * Retrieve arguments with prefix
     *
     * @return Argument[]
     */
    public function withPrefix()
    {
        return $this->filterArguments(['hasPrefix']);
    }

    /**
     * Retrieve arguments without prefix
     *
     * @return Argument[]
     */
    public function withoutPrefix()
    {
        return $this->filterArguments(['noPrefix']);
    }

    /**
     * Find all required arguments that don't have values after parsing.
     *
     * These arguments weren't defined on the command line.
     *
     * @return Argument[]
     */
    public function missing()
    {
        return $this->filterArguments(['isRequired', 'noValue']);
    }

    /**
     * Filter defined arguments as to whether they are required or not
     *
     * @param string[] $filters
     *
     * @return Argument[]
     */
    protected function filterArguments($filters = [])
    {
        $arguments = $this->arguments;

        foreach ($filters as $filter) {
            $arguments = array_filter($arguments, [$this, $filter]);
        }

        if (in_array('hasPrefix', $filters)) {
            usort($arguments, [$this, 'compareByPrefix']);
        }

        return array_values($arguments);
    }

    /**
     * Determine whether an argument as a prefix
     *
     * @param Argument $argument
     *
     * @return bool
     */
    protected function noPrefix($argument)
    {
        return !$argument->hasPrefix();
    }

    /**
     * Determine whether an argument as a prefix
     *
     * @param Argument $argument
     *
     * @return bool
     */
    protected function hasPrefix($argument)
    {
        return $argument->hasPrefix();
    }

    /**
     * Determine whether an argument is required
     *
     * @param Argument $argument
     *
     * @return bool
     */
    protected function isRequired($argument)
    {
        return $argument->isRequired();
    }

    /**
     * Determine whether an argument is optional
     *
     * @param Argument $argument
     *
     * @return bool
     */
    protected function isOptional($argument)
    {
        return !$argument->isRequired();
    }

    /**
     * Determine whether an argument is optional
     *
     * @param Argument $argument
     *
     * @return bool
     */
    protected function noValue($argument)
    {
        return $argument->values() == [];
    }

    /**
     * Compare two arguments by their short and long prefixes.
     *
     * @see usort()
     *
     * @param Argument $a
     * @param Argument $b
     *
     * @return int
     */
    public function compareByPrefix(Argument $a, Argument $b)
    {
        if ($this->prefixCompareString($a) < $this->prefixCompareString($b)) {
            return -1;
        }

        return 1;
    }

    /**
     * Prep the prefix string for comparison
     *
     * @param Argument $argument
     *
     * @return string
     */
    protected function prefixCompareString(Argument $argument)
    {
        return mb_strtolower($argument->longPrefix() ?: $argument->prefix() ?: '');
    }
}
climate/src/Argument/Manager.php000064400000013222147361032630012623 0ustar00<?php

namespace League\CLImate\Argument;

use League\CLImate\CLImate;
use League\CLImate\Exceptions\InvalidArgumentException;

class Manager
{
    /**
     * An array of arguments passed to the program.
     *
     * @var Argument[] $arguments
     */
    protected $arguments = [];

    /**
     * A program's description.
     *
     * @var string $description
     */
    protected $description;

    /**
     * Filter class to find various types of arguments
     *
     * @var \League\CLImate\Argument\Filter $filter
     */
    protected $filter;

    /**
     * Summary builder class
     *
     * @var \League\CLImate\Argument\Summary $summary
     */
    protected $summary;

    /**
     * Argument parser class
     *
     * @var \League\CLImate\Argument\Parser $parser
     */
    protected $parser;

    public function __construct()
    {
        $this->filter  = new Filter();
        $this->summary = new Summary();
        $this->parser  = new Parser();
    }

    /**
     * Add an argument.
     *
     * @param Argument|string|array $argument
     * @param $options
     *
     * @return void
     * @throws InvalidArgumentException if $argument isn't an array or Argument object.
     */
    public function add($argument, array $options = [])
    {
        if (is_array($argument)) {
            $this->addMany($argument);
            return;
        }

        if (is_string($argument)) {
            $argument = Argument::createFromArray($argument, $options);
        }

        if (!$argument instanceof Argument) {
            throw new InvalidArgumentException('Please provide an argument name or object.');
        }

        $this->arguments[$argument->name()] = $argument;
    }

    /**
     * Add multiple arguments to a CLImate script.
     *
     * @param array $arguments
     */
    protected function addMany(array $arguments = [])
    {
        foreach ($arguments as $name => $options) {
            $this->add($name, $options);
        }
    }

    /**
     * Determine if an argument exists.
     *
     * @param string $name
     * @return bool
     */
    public function exists($name)
    {
        return isset($this->arguments[$name]);
    }

    /**
     * Retrieve an argument's value.
     *
     * @param string $name
     * @return string|int|float|bool|null
     */
    public function get($name)
    {
        return isset($this->arguments[$name]) ? $this->arguments[$name]->value() : null;
    }

    /**
     * Retrieve an argument's all values as an array.
     *
     * @param string $name
     * @return string[]|int[]|float[]|bool[]
     */
    public function getArray($name)
    {
        return isset($this->arguments[$name]) ? $this->arguments[$name]->values() : [];
    }

    /**
     * Retrieve all arguments.
     *
     * @return Argument[]
     */
    public function all()
    {
        return $this->arguments;
    }

    /**
     * Determine if an argument has been defined on the command line.
     *
     * This can be useful for making sure an argument is present on the command
     * line before parse()'ing them into argument objects.
     *
     * @param string $name
     * @param array $argv
     *
     * @return bool
     */
    public function defined($name, array $argv = null)
    {
        // The argument isn't defined if it's not defined by the calling code.
        if (!$this->exists($name)) {
            return false;
        }

        $argument = $this->arguments[$name];
        $command_arguments = $this->parser->arguments($argv);

        foreach ($command_arguments as $command_argument) {
            if ($this->isArgument($argument, $command_argument)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if the defined argument matches the command argument.
     *
     * @param Argument $argument
     * @param string $command_argument
     *
     * @return bool
     */
    protected function isArgument($argument, $command_argument)
    {
        $possibilities = [
            $argument->prefix()     => "-{$argument->prefix()}",
            $argument->longPrefix() => "--{$argument->longPrefix()}",
        ];

        foreach ($possibilities as $key => $search) {
            if ($key && strpos($command_argument, $search) === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Retrieve all arguments as key/value pairs.
     *
     * @return array
     */
    public function toArray()
    {
        $return = [];

        foreach ($this->all() as $name => $argument) {
            $return[$name] = $argument->value();
        }

        return $return;
    }

    /**
     * Set a program's description.
     *
     * @param string $description
     */
    public function description($description)
    {
        $this->description = trim($description);
    }

    /**
     * Output a script's usage statement.
     *
     * @param CLImate $climate
     * @param array $argv
     */
    public function usage(CLImate $climate, array $argv = null)
    {
        $this->summary
                ->setClimate($climate)
                ->setDescription($this->description)
                ->setCommand($this->parser->command($argv))
                ->setFilter($this->filter, $this->all())
                ->output();
    }

    /**
     * Parse command line arguments into CLImate arguments.
     *
     * @param array $argv
     */
    public function parse(array $argv = null)
    {
        $this->parser->setFilter($this->filter, $this->all());

        $this->parser->parse($argv);
    }

    /**
     * Get the trailing arguments
     *
     * @return string|null
     */
    public function trailing()
    {
        return $this->parser->trailing();
    }
}
climate/src/Argument/Summary.php000064400000011617147361032630012714 0ustar00<?php

namespace League\CLImate\Argument;

use League\CLImate\CLImate;

class Summary
{
    /**
     * @var \League\CLImate\CLImate $climate
     */
    protected $climate;

    /**
     * @var string $description
     */
    protected $description;

    /**
     * @var string $command
     */
    protected $command;

    /**
     * @var Filter $filter
     */
    protected $filter;

    /**
     * @param \League\CLImate\CLImate $climate
     *
     * @return \League\CLImate\Argument\Summary
     */
    public function setClimate(CLImate $climate)
    {
        $this->climate = $climate;

        return $this;
    }

    /**
     * @param string $description
     *
     * @return \League\CLImate\Argument\Summary
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * @param string $command
     *
     * @return \League\CLImate\Argument\Summary
     */
    public function setCommand($command)
    {
        $this->command = $command;

        return $this;
    }

    /**
     * @param Filter $filter
     * @param Argument[] $arguments
     *
     * @return \League\CLImate\Argument\Summary
     */
    public function setFilter($filter, $arguments)
    {
        $this->filter = $filter;
        $this->filter->setArguments($arguments);

        return $this;
    }

    /**
     * Output the full summary for the program
     */
    public function output()
    {
        // Print the description if it's defined.
        if ($this->description) {
            $this->climate->out($this->description)->br();
        }

        // Print the usage statement with the arguments without a prefix at the end.
        $this->climate->out("Usage: {$this->command} "
                            . $this->short($this->getOrderedArguments()));

        // Print argument details.
        foreach (['required', 'optional'] as $type) {
            $this->outputArguments($this->filter->{$type}(), $type);
        }
    }

    /**
     * Build a short summary of a list of arguments.
     *
     * @param Argument[] $arguments
     *
     * @return string
     */
    public function short($arguments)
    {
        return implode(' ', array_map([$this, 'argumentBracketed'], $arguments));
    }

    /**
     * Build an argument's summary for use in a usage statement.
     *
     * For example, "-u username, --user username", "--force", or
     * "-c count (default: 7)".
     *
     * @param Argument $argument
     *
     * @return string
     */
    public function argument(Argument $argument)
    {
        $summary     = $this->prefixedArguments($argument);
        $printedName = mb_strstr($summary, ' ' . $argument->name());

        // Print the argument name if it's not printed yet.
        if (!$printedName && !$argument->noValue()) {
            $summary .= $argument->name();
        }

        if ($defaults = $argument->defaultValue()) {
            if (count($defaults) == 1) {
                $summary .= " (default: {$defaults[0]})";
            } else {
                $summary .= ' (defaults: ' . implode(', ', $defaults) . ')';
            }
        }

        return $summary;
    }

    /**
     * Build argument summary surrounded by brackets
     *
     * @param Argument $argument
     *
     * @return string
     */
    protected function argumentBracketed(Argument $argument)
    {
        return '[' . $this->argument($argument) . ']';
    }

    /**
     * Get the arguments ordered by whether or not they have a prefix
     *
     * @return Argument[]
     */
    protected function getOrderedArguments()
    {
        return array_merge($this->filter->withPrefix(), $this->filter->withoutPrefix());
    }

    /**
     * Print out the argument list
     *
     * @param array $arguments
     * @param string $type
     */
    protected function outputArguments($arguments, $type)
    {
        if (count($arguments) == 0) {
            return;
        }

        $this->climate->br()->out(mb_convert_case($type, MB_CASE_TITLE) . ' Arguments:');

        foreach ($arguments as $argument) {
            $this->climate->tab()->out($this->argument($argument));

            if ($argument->description()) {
                $this->climate->tab(2)->out($argument->description());
            }
        }
    }

    /**
     * Builds the summary for any prefixed arguments
     *
     * @param Argument $argument
     *
     * @return string
     */
    protected function prefixedArguments(Argument $argument)
    {
        $prefixes = [$argument->prefix(), $argument->longPrefix()];
        $summary  = [];

        foreach ($prefixes as $key => $prefix) {
            if (!$prefix) {
                continue;
            }

            $sub = str_repeat('-', $key + 1) . $prefix;

            if (!$argument->noValue()) {
                $sub .= " {$argument->name()}";
            }

            $summary[] = $sub;
        }

        return implode(', ', $summary);
    }
}
climate/src/Logger.php000064400000011725147361032630010714 0ustar00<?php

namespace League\CLImate;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use function array_key_exists;
use function is_array;
use function str_replace;
use function strpos;

/**
 * A PSR-3 compatible logger that uses CLImate for output.
 */
class Logger extends AbstractLogger
{
    /**
     * @var array $levels Conversion of the level strings to their numeric representations.
     */
    private $levels = [
        LogLevel::EMERGENCY => 1,
        LogLevel::ALERT => 2,
        LogLevel::CRITICAL => 3,
        LogLevel::ERROR => 4,
        LogLevel::WARNING => 5,
        LogLevel::NOTICE => 6,
        LogLevel::INFO => 7,
        LogLevel::DEBUG => 8,
    ];

    /**
     * @var int $level Ignore logging attempts at a level less than this.
     */
    private $level;

    /**
     * @var CLImate $climate The underlying climate instance we are using for output.
     */
    private $climate;

    /**
     * Create a new Logger instance.
     *
     * @param string $level One of the LogLevel constants
     * @param CLImate $climate An existing CLImate instance to use for output
     */
    public function __construct($level = LogLevel::INFO, CLImate $climate = null)
    {
        $this->level = $this->convertLevel($level);

        if ($climate === null) {
            $climate = new CLImate;
        }
        $this->climate = $climate;

        # Define some default styles to use for the output
        $commands = [
            "emergency" => ["white", "bold", "background_red"],
            "alert" => ["white", "background_yellow"],
            "critical" => ["red", "bold"],
            "error" => ["red"],
            "warning" => "yellow",
            "notice" => "light_cyan",
            "info" => "green",
            "debug" => "dark_gray",
        ];

        # If any of the required styles are not defined then define them now
        foreach ($commands as $command => $style) {
            if (!$this->climate->style->get($command)) {
                $this->climate->style->addCommand($command, $style);
            }
        }
    }


    /**
     * Get a numeric log level for the passed parameter.
     *
     * @param string $level One of the LogLevel constants
     *
     * @return int
     */
    private function convertLevel($level)
    {
        # If this is one of the defined string log levels then return it's numeric value
        if (!array_key_exists($level, $this->levels)) {
            throw new InvalidArgumentException("Unknown log level: {$level}");
        }

        return $this->levels[$level];
    }


    /**
     * Get a new instance logging at a different level
     *
     * @param string $level One of the LogLevel constants
     *
     * @return Logger
     */
    public function withLogLevel($level)
    {
        $logger = clone $this;
        $logger->level = $this->convertLevel($level);
        return $logger;
    }


    /**
     * Log messages to a CLImate instance.
     *
     * @param string $level One of the LogLevel constants
     * @param string|object $message If an object is passed it must implement __toString()
     * @param array $context Placeholders to be substituted in the message
     *
     * @return void
     */
    public function log($level, $message, array $context = [])
    {
        if ($this->convertLevel($level) > $this->level) {
            return;
        }

        # Handle objects implementing __toString
        $message = (string)$message;

        # Handle any placeholders in the $context array
        foreach ($context as $key => $val) {
            $placeholder = "{" . $key . "}";

            # If this context key is used as a placeholder, then replace it, and remove it from the $context array
            if (strpos($message, $placeholder) !== false) {
                $val = (string)$val;
                $message = str_replace($placeholder, $val, $message);
                unset($context[$key]);
            }
        }

        # Send the message to the climate instance
        $this->climate->{$level}($message);

        # Append any context information not used as placeholders
        $this->outputRecursiveContext($level, $context, 1);
    }


    /**
     * Handle recursive arrays in the logging context.
     *
     * @param string $level One of the LogLevel constants
     * @param array $context The array of context to output
     * @param int $indent The current level of indentation to be used
     *
     * @return void
     */
    private function outputRecursiveContext($level, array $context, $indent)
    {
        foreach ($context as $key => $val) {
            $this->climate->tab($indent);

            $this->climate->{$level}()->inline("{$key}: ");

            if (is_array($val)) {
                $this->climate->{$level}("[");
                $this->outputRecursiveContext($level, $val, $indent + 1);
                $this->climate->tab($indent)->{$level}("]");
            } else {
                $this->climate->{$level}((string)$val);
            }
        }
    }
}
climate/src/ASCII/the-league.txt000064400000012037147361032630012372 0ustar00hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyysoo++//////++oosyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo+/-.``                ``.-:+oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs+:.`                              `.:+shhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhyo:.`             ``  `.`  `.`             `.:oyhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhyo:`           .   :/-. -/./`./.-. /` .           `:oyhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhy+.        `.`   /-  `o-. +:-+-/-`/:-/ `+ ::-.         ./yhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhs/`       ` .+--   `+.-./--`.` ```---`::./.:/-.`   `       `/shhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhy/`       . .+.-+--.  ```   ```.....``   ````/:-`  `/-:-       `/yhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhy+.      `- :::-:--.`   .-:/++++//////++++/:-.` ``  `+. -+`.`      `+yhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhy-       -//` ./` `  .:+++:-.`` `  `  `  ``.-:+++:.   -:::-/::-       -shhhhhhhhhhhhhh
hhhhhhhhhhhhho`        ` `:: `  `:++:.````.`...........`.`````.:++:.   -.`- `        `ohhhhhhhhhhhhh
hhhhhhhhhhhh+`             `  .+o:.```.`..`..`................`  .:o+.                 +hhhhhhhhhhhh
hhhhhhhhhhh+                .+o-``.`..`..`..`..`..`..`...........```-o+.                /hhhhhhhhhhh
hhhhhhhhhh+               `/s- `..`..`..`..`...--:::::--.``........```-s/`               +hhhhhhhhhh
hhhhhhhhhs               `o+```..`..`..`.-/++oooooooooooo+/-..`..`.`````+s.               ohhhhhhhhh
hhhhhhhhy.              .y: .`..`..`..`:ooooooooooooooooo+/:-::-...-`..` :y.              `yhhhhhhhh
hhhhhhhh/      .       .y: .`.`.:---. /oooooooooooooooo/::/:-:++:..`..... -y.       .      /hhhhhhhh
hhhhhhhy`   `.:s:``    s/ .```+ooooo/-ooooooooooooooo+::/+./ooooo+...`..`. /y`   `.:s:``    yhhhhhhh
hhhhhhh+   `/ooooo/`  /s ..` :ooooo+`ooooooooooooooo//:+/.+oooooooo.`..`..` s/  `/ooooo/`   +hhhhhhh
hhhhhhh.     oooo/    h-`.`` :ooooo.:-ooooooooooooo:--/+`ooooooooooo`.`..`.`-h`   oooo/     .hhhhhhh
hhhhhhh     `.  `-   -h ``.. `+oos.///-/oooooooooo-----./oooooooooos.`..`..` y:  `.  `-      yhhhhhh
hhhhhhy              +s `..``  :oo:-///:-:+oooooo-/-/o-.sooooooooooo ..`..`. o+              yhhhhhh
hhhhhhy              ++ ..`..   `:o/-/////:-:/oo-/:/o+ oooooooooooo-`.`..`.. ++              shhhhhh
hhhhhhy              +o .`..`..   `-:.-.-:///:.-/::oo.-ooooooosooo+`.`..`..` o+              yhhhhhh
hhhhhhh              -y `..`..`..:+ooo+../-:/--//:oo/.:::///::-...``  ` ..`. y:              yhhhhhh
hhhhhhh.             `h-`.`..`-+ooo+/--:/-/:`://-ooo-`.`.-:::/++ooooo+:-``.`.h`             .hhhhhhh
hhhhhhh+         `    /s `..`.---.-:://///::/:::ooo:-+oooooooooooooooooo:`. s+              /hhhhhhh
hhhhhhhy     .:/:/`   `y/ .`.`-/-/-////////:/-/ooo/:`://++oooooooooooo/-`. :y`   `/`::`     yhhhhhhh
hhhhhhhh/    `/`.`:    .y- ..`.:/-/-////////.ooooo:-.`..`....:::///:-..`. -y.    .::`/`    :hhhhhhhh
hhhhhhhhy`    `--/:`    .y: `..-:/-//://///:/oooo:/.`..`..`..`..`..`..`. :y-    /.:`-`    `yhhhhhhhh
hhhhhhhhhs     `::.--    .s+````.:/::++/::/+ooo+::.`..`..`..`..`..`..```/s.    ..:/-:     ohhhhhhhhh
hhhhhhhhhh+     ``.-/:    `/s-````-:::/ooooo++/:-.`..`..`..`..`..`..` -s+`    /-`:/`     +hhhhhhhhhh
hhhhhhhhhhh/     ..``::.    .oo-```.-:::::::--...`..`..`..`..`..`.``-oo.    ..-:--:     /hhhhhhhhhhh
hhhhhhhhhhhh+      -:/.+`     .+o:`  `.``..`.......`..`..`..`.````:o+-     `+.::-`     /hhhhhhhhhhhh
hhhhhhhhhhhhho`     `:.---:     ./++:.`````.`...........`.``` .:+o/.    ` :-.:/ `    `ohhhhhhhhhhhhh
hhhhhhhhhhhhhhs-      -::-:`..     .:++/:..``  `` `  ` ``..:/++:.     ``-::::.`     -shhhhhhhhhhhhhh
hhhhhhhhhhhhhhhy+`      :.-:.-: `     `.-//+++////////+++//-.`     ` ::` .:-      `+yhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhy:`      /-.:./-:. `        ``......``        `` /-:-:--:      `:yhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhs:`     ```/-/:.:--- .`                 ` `/././::/```     `:shhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhs/.        : -:`-:`+`:: -` --` :-:`:.-:  /:-` -        ./shhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhyo:`       `.--`/- +.:+ /-.+ +-/- -+    -         `-oyhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhyo:``        `` . .- /../ :`.-  .          ``:oyhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhys+:``                              ``-+syhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhso+:.``                  ``.:/oshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhysso++////////++oosyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
climate/src/ASCII/php000064400000000163147361032630010320 0ustar00           __
    ____  / /_  ____
   / __ \/ __ \/ __ \
  / /_/ / / / / /_/ /
 / .___/_/ /_/ .___/
/_/         /_/climate/src/ASCII/failed.txt000064400000000367147361032630011601 0ustar00  ______      _____ _      ______ _____
 |  ____/\   |_   _| |    |  ____|  __ \
 | |__ /  \    | | | |    | |__  | |  | |
 |  __/ /\ \   | | | |    |  __| | |  | |
 | | / ____ \ _| |_| |____| |____| |__| |
 |_|/_/    \_\_____|______|______|_____/climate/src/ASCII/the-league-big.txt000064400000050026147361032630013131 0ustar00hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyyyyyyyyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyssoo+/:--...`````````````..--:/++ossyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyso/:-.```                                ```.-:/osyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyso/-.``                                                ``.-/+syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhso/-``                                                            ``-:osyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs+:.`                                                                      `.:+syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo:.`                                                                              `.:oyhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+-`                              ```.`    `:::`     `....`                               `-+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+-`                              `+++///.   .o+-/o-   -o+://++`  .:`                           `-+shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo:`                         ::      `oo.```   .o+`  /s- `+o`  ``.   +s`   ./.                        `:oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy/.                            /s.      :s+/++.  -s/:::/s/ .s/  `/+/` -s:    +s`  -/-``                    ./shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhys:`                  ``-//`      `o+`     .s/````` -s/----o+ .s/`  .+o``o+`   -s:  :s/:++/-                    `-oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo-                   `:++:-`        -s:  ``. oo//++/`.+.   `/:  -++++++- -s/`  `o+` -so.```-.                       .oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo.                 `-`  :s:`.:+`       /s//++/`.-.```               ````    :+++/+s. -s/:/+/`                           .oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho.                   .oo-  :oo+:.  `     `-.``                                  ``..` .so-` ``       `/+++-`                .oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs-                `:`   `/s:` -s/`./++.                  ```..........```                .-/++/       -o+.`-+o/                 .shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy:                ` .oo-`-/o/+o. -o+/-`           ``.-/+ossyyyhhyyyyyyhhyyysso+/:.``           `.     `+s-    `os`                  -yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh+`               .o+` `:oo+-` `:s: `          `.:+syhhyyso+/::--........--::/+osyyhhys+/.`             -y:    .oo-  -+-               `/hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy-               .os/`    .+o-    `         .:+shhys+/-.`                          `.-:+syhhso:.         `/o+:-/s/` :oo:oo-               -shhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhho`              `+o--oo-`    :o-         `-+shhs+:.`          ``.`  ...` `...   ``        `.:+shhs+-`        -//:.`:ooo-  .o/               `+hhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhh:                 `    -oo-`           `:oyhyo:.      ``` `.--` `--.` .--.` .--.` .--.   `      `:oyhho:`         -o+-`-oo`                    -hhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhy-                         -o+`        -oyhy+-`     `.` `.--` `.--` `--.` `--.` .--.` .--.  .-.`     `-/yhyo-`      `.     `                      .shhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhs`                            `      `:yhyo-`    `.` `.--` `.--` `.--` `--.` `--.` .--.` .--.` .--.      `.+yhy/.                                   `ohhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhh+`                                  .+yhs/`   `..``.--. `.--` `.--` `.--` `---` `--.` .--.` .--.` .--.   `   `:shh+.                                  `+hhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhho                                  .+hho-   ``` .--.``.--. `.--` `.--` `.--` `.--` `--.` `--.` .--.` .--.` `.`   -ohh+.                                  +hhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhh+`                                `/yho-   ` `--.``.--.``.--.``.--` `.--` `.--` `.--` `--.` `--.` .--.` .--.``..`   -ohh/`                                 /hhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhho                                 -yhs:    .-.` `--.` .--.``.--.``.--` `...  `..````..`  `.-.` `--.` .--.` .--.``..`   -shy:                                 +hhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhho`                               `ohy/`  `.` `--.` `--.` .--.``.-.` ``...-:://++++++++++//:-..``  `--.` `--.` .--.``.-`  `/yho`                               `ohhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhy`                               -shs.  ` `--.` `--.` `--.``.-. `.-:/++ooooooooooooooooooooooooo+:-```--.` `-.`  `.`   ..`  .shy:                               `yhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhy-                               /hh+`  `--` `.-.` `--.` `--. `-/+oooooooooooooooooooooooooooooooooso+-``.``````.....--` `..  `/hh/`                              -yhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhh/                               +hy:  `` `.--` `.-.` `--.` ``-osoooooooooooooooooooooooooooooooooo+/:--..--::/:::---... ..````  :yho`                              :hhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhs                              `+hy-   .--. `.--` `.-.` `-- `+soooooooooooooooooooooooooooooooooo/--:--:/+/-..` .` `...` `--.`    .yho`                              shhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhh-                              ohy-  ..``.--` `..` ``...` ``+soooooooooooooooooooooooooooooooo+:-::-:+o:.`.:+o/``.-` `--.` `--.`   .yho`                             -yhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhh+             -/               /hh-   .--. ```-/++++++/:-.  /soooooooooooooooooooooooooooooooo--/:-/oo-``-+oooooo:``--` `--.` `--.`  .yh+`              -/             +hhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhh.            -ss:             :hh:  ..``.`  -osooooooooos+ -sooooooooooooooooooooooooooooooo/./+.:os:``-osooooooos+-`.--` `.--` `--.  :hh/             -ss/`           `hhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhho       ..---/ooos/.````      `hh+   .--.   -sooooooooooo+`.oooooooooooooooooooooooooooooooo--o:.+so- .+sooooooooooos:``.--` `.--` `-.  /hh.      ..---/ooos/.````       ohhhhhhhhhhhhhhh
hhhhhhhhhhhhhhh:       -osssoooooosooo:`    `ohy` `.` .`  `ooooooooooooo` osoooooooooooooooooooooooooooos+.:s--oso` :ooooooooooooooos+` `.--` `.--` ``  sho`     -osssoooooosooo:`      -hhhhhhhhhhhhhhh
hhhhhhhhhhhhhhy`         -oooooooooo:.      -hh:  `--.`   .sooooooooooo-  +sooooooooooooooooooooooooooos/`+s-.ss+` :soooooooooooooooos+`  `.--` `.--`   -hh:       -oooooooooo:.        `shhhhhhhhhhhhhh
hhhhhhhhhhhhhh+           /sooooooo.        shs` `` `--   .sooooooooos- :`.oooooooooooooooooooooooooooo:`:s-.oso` :soooooooooooooooooos+ `. `.--` `.--` `ohs        /sooooooo.           /hhhhhhhhhhhhhh
hhhhhhhhhhhhhd-          .sooo/+oos-       .hd:  .--` `   `+oooooooos: -+/``+soooooooooooooooooooooooo.`:..`:/+- -soooooooooooooooooooos- --` `.--` `.-` -dh-      `sooo/+oos-           -dhhhhhhhhhhhhh
hhhhhhhhhhhhhh.          -/-`   .-+/       /dh`   `---`    .soooooos/ -////-`-ooooooooooooooooooooooo.`.::::---``oooooooooooooooooooooos/ `.--` `.--` ``  yh+      -/-`   .-+/           `hhhhhhhhhhhhhh
hhhhhhhhhhhhhy                            `ohs  .-` `.--  ` .oooooos- ///////.`:ooooooooooooooooooso`-+:``..---`/sooooooooooooooooooooos: ` `.--. `.--`   ohs`                            yhhhhhhhhhhhhh
hhhhhhhhhhhhhs                            .yh+  `.--` `-.    `+sooos/ -///////:.`-+sooooooooooooos+`-//:`+so+` .soooooooooooooooooooooos` --.  .--. `.--  /hy.                            ohhhhhhhhhhhhh
hhhhhhhhhhhhho                            .hh:  . `.--` `.     -osoos- ://///////-``/osooooooooos+`://:`+sos: `+oooooooooooooooooooooos/   .--.  .--. `.` -hh-                            ohhhhhhhhhhhhh
hhhhhhhhhhhhho                            .hh- `--` `.--`        -osos: -///////////-``:+ssoooos/`:///`/soso  :sooooooooooooooooooooooo``-.` .--.  .--.   -hh-                            +hhhhhhhhhhhhh
hhhhhhhhhhhhho                            .hh-   .--. `.--`        .+ss+.`:////////////-``-/oss/`:///`/soos-  osooooooooooooooooooooos-  `--.` `--.` .--` -hh-                            +hhhhhhhhhhhhh
hhhhhhhhhhhhho                            .hh:  .  .--. `.--`        `/os/``----://///////:-.`.`////`:soos/` :sooooooooooooooooooooos+ .-` `--.` `--.` .` -hh-                            ohhhhhhhhhhhhh
hhhhhhhhhhhhhs                            .yh+  --.  .--. `.--`     `   ..-:::..:`.//////////..////`:sooos```ooooooooooooooosssooooso. `.--` `--.` `--.`  /hh.                            ohhhhhhhhhhhhh
hhhhhhhhhhhhhy                            `sho   .--.` .--.  .--`  `-/+oooooos:`-`+..//////:`-////`-sooos:``.++oosssssoo+/:-..``....` ``  ``.` `--.` `--  ohs`                            shhhhhhhhhhhhh
hhhhhhhhhhhhhh`                            /dh` `` .--.` .--.  `.:+oooooooss/.-.`.+s/.-::/-`:////.-oooooo`-`--..``......---:::--.....``          `--.` `  yh+                            `hhhhhhhhhhhhhh
hhhhhhhhhhhhhd-                            .hd- `-.` `--.` ```:+oooooooso+:.-..://.-oo. ` ./////..ooooos--.`..```..........-::/+++oooooo++/:.`     `--.  -hd-                            .hhhhhhhhhhhhhh
hhhhhhhhhhhhhh+                             sho` `--.` `--  -ooooosso+/:-.-.-://///-.+s: -/////-.ooooos+`: ``....-:::/++oooooooooooooooooooooo+/.``` `.  ohy                             /hhhhhhhhhhhhhh
hhhhhhhhhhhhhhy`                            -hh-   `--.`  -.:+///::--.-..-://///////:`/s/`:///..ooooooo.:.:ooooooooooooooooooooooooooooooooooooos- --`  -hh:                            `shhhhhhhhhhhhhh
hhhhhhhhhhhhhhh-                  ``        `ohs  `` `--.``....```-`.-:///////////////`:s/`//.-oooooos-./`osooooooooooooooooooooooooooooooooooos/` `.`  shs`         `                  -hhhhhhhhhhhhhhh
hhhhhhhhhhhhhhho           `.-:/+++o`        .hh/  .-` `-.`.`::::`+/`/////////////////:`/s-..:sooooos+`+`.++oossooooooooooooooooooooooooooooos+- .-.   /hh.        `++` -::-            +hhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhh.         `++:-o/``o:         /hh:  .-.` ``..-///-.o-./////////////////:`o+.+oooooooo.// `.....-:/++ooosoooooooooooooooooooo/.`` `..  -yh/         :o` -s:-o/          `hhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhh+          -s. -o` .+`        `+hy.  `.--`  -`:///..s:./////////////////`:soooooooos.-o`` `.--`  `.....-::/++oooooooooo++:.`` .--.   .yho`         -o//o- .s:          /hhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhy-          +/  `  `:/`        `ohy.   `.--``-`:///.-s:.:///////////////`:sooooooos:.s- --` `.--` `.--`  `..````........` `--.``..  .shs`        `-``.-. `+/          .yhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhs           ``.```++-`         `ohy.    `.-.`:`////.-s+../////////////:`+ooooooos:`o/  `.--` `.--` `.--` `.-.` `.-.` `--.` .--.   .yho`        `/+` -`  ``           ohhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhh:          `+///+s:``.         `ohy:  ````...:.:///.-+s+--://///////-.+soooooos/.++`.-.``.--```.--` `.--` `.-.` `--.` `--.` .`  -yho`         /s. :o` -o`          :hhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhy-             .s/:/++.         `/hh/`  .. ``.:.-///-./oo+:---::::--:ooooooooo:.o+```.--.``.--```.--` `.--` `.--` `--.` `--`   :hh+`          -/++o/..o:          .yhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhy`          `/o/`  ` //`         :yho.  `..  `--.:///-./oso++////+oooooooos+--o/`.-.``.--.``.--` `.--` `.--` `.--` `--.` `  `ohy:          -/` `..:/+:          `shhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhho`          ..     `:s+`         .shy/   `..  .----:/:-.:+oooooooooooooo+:-+o-```.--.``.--.``.--. `.--` `.--` `.--` `.`   :yhs.         `/+- .++:. `          `+hhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhh+              .-/+/-/o`         `:yho-   `..  `-:::---.:+oooooooooo+:-:++:``--.` .--.``.--.``.--. `.--` `.--` `.-.    -ohy/`          /s.  ..`+s`           +hhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhh/           `++/.`   -:-          .+hho.   `..  `.:/++ooooo++///::-://:.`..` `--.` .--.``.--.``.--. `.--` `.--` `   .+hh+.            `/++-.`.o/           /hhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhh+           `     ./+/+/`          .+hho-   `.` `....``````..----..`..` `--.` `--.` `--.` .--.``.--. `.--. `.`   .+hho-          `---.``.:+++-           /hhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhh+`            `:+o+.  /s.           -+hho:`   `` `.--.  .-.`  .--` `--.` `--.` `--.` `--.` .--.``.--.``..`   `-ohho-           .s/::/++:`              /hhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhho`           :/.`++.-+o`             ./yhy+.       .--. `.--. `.--` `.--` `--.` `--.` `--.` `--.` .-.     ./shy+.              +o  .o//o-            +hhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhs.             .o+::. ``.-.           `:shhs/.      ..-. `.--. `.--` `.--` `--.` `--.` `--.` `-.      .:shhs:`            /:` `++/o-  `           .ohhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhy-            ::   .+++/:o/             `/shhs+-`      `   .--. `.--` `.--` `---` `--.` `.`      `-+shhs/`               `/o-  ./o+`            .yhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhh+              -+oo:` .s:                .:oyhhs/-`         ``  `...` `.-.` `..`           `-/oyhyo:.              :o-  .so:-` ``            /hhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs.            .- `/o:o+` .//:`             `.:oyhhyo/:.`                            `.:/oyhhyo/.`             `-/- .+o++.`-/+.            .shhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh/`              -o+- `+o-.:o/.               `.-/osyhhyso+/:--..````````..--:/+osyhhyso/:.`               `/o:.`   .+o-`              `/hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy-             ..  -o/`   .s:  .`                 `.-/+ossyyhhyyyyyyyyyyhyyysso+/-.`                     .s:      ` ./.             -shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho.               +o.  `/o:``+++/-`                      ``..............``                       -/+/-` .o+.  `++               .ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+`             `:++/++. .o+``.+o` ``                                                      ``   o+`./o/``:o/++:`             `+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+.             `...  -o++:.-o/``/o//:.                                              .-///++` oo .:+oo` `..`             `+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+.                `o- `+o:-  /s.`.:s- .:-.`                                 `.    :s:` `o/`.oo/-.`/:                .+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo-`                  o/   :s-   -s- `+o:.`+/`   .`    ```    `.....  /-   :s.    +o.-/o:  `o+                   .oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs:`                ``  -s/.` -s-  .s:  .oo+  .s.  `+++o.  `o+:::o/ :o/.`o/     `+s:.`    ``                `:syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+-`                 `.://++:  `++`  /o`+/`:o  .o/` :s-  o+  `/o` `:+oo`      `o/                     `-+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs+.`                   `   -/o/`  s/ `o/o/  :s:---s+  /s//+s-     +o        ``                  `./shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhys/-`                     ``.` `+.  `+o.  /s----s/  :s. `/+`    .:                         `-/syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs+-.                             `.   .-    :-  `:`   .`                            .-+shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhys+-.`                                                                       `-/oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhys+:.`                                                              `.-+oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhso+:.``                                                  ``.:/oshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyso+:-.```                                  ```.-:/osyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyso++::-..``````````````````..-::/+osyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyyyyyssyyyyyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
climate/src/ASCII/passed.txt000064400000000375147361032630011633 0ustar00  _____         _____ _____ ______ _____
 |  __ \ /\    / ____/ ____|  ____|  __ \
 | |__) /  \  | (___| (___ | |__  | |  | |
 |  ___/ /\ \  \___ \\___ \|  __| | |  | |
 | |  / ____ \ ____) |___) | |____| |__| |
 |_| /_/    \_\_____/_____/|______|_____/climate/src/ASCII/the-league-massive.txt000064400000371413147361032630014045 0ustar00hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyyysssooo+++++++//////////++++++++oossssyyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyso++//::--....```                                  ````....--://++ossyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhhhhhhyyysoo+/::--..``                                                                ``..---://oooyyyhhhhhhhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyso++::-..```                                                                                    ```..-::/+osyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyso+/:-..```                                                                                                    ```..--/+osyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhddhyss/:-.```                                                                                                                    ```.-:/osyhhdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhhhhyo+--`                                                                                                                                      `.-+oyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhhhys++--`                                                                                                                                                `.-/+syyhhhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhyys+/--`                                                                                                                                                          `.-/+syyhhhhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhdhhyo+-..`                                                                                                                                                                  ``.-/+syhddhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhddhso:-```                                                                                                                                                                          ```--oshddhhyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdyy/:.`                                                                                                                                                                                      ``:/syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhhso/.`                                                                                                                                                                                              `.:+ohhhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhyyo/:``                                                                                                                                                                                                    ``:/osyhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdhyo+-.`                                                                                                                                                                                                            `.-+oyhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdhh+/.``                                                                                                                                                                                                                  ``./+yhdhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdhy+-.`                                                                                                     `..---..                   `                                                                                          `-+sydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhso-.`                                                                               ``````..:::.`          `./ossssss:-             ``-:///:::--..``                                                                                 `.-oshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhhhhso:.`                                                                            `.--::/+++oooossy:.          :+yss+++sss+-`         ``/+ssyssssssssoo:-``        ```                                                                       .:+ohhhhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhhhy+:``                                                                               .+sysssssooo++//:.`        ..ssyo+```/oyss:.        -/yyso+::://++ossys+.       .///.`                                                                        `:/shhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdhh+/.`                                                                                  ./sys+:-...````           `/oyss-`   ..ssyo+`       /oys+-`    ```.-:s+:`      `:syy/-                                                                          `.:/hhdhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhys:-`                                                                --/-.                `-oys+.`                 ./oyso-`      :/syy:-    `.+syo:`           ``        :+yss.`         `-::.`                                                               .:oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhy/:`                                                                 ``osyo+                `.+syo:`   ``````       `-osy+:         .+syo/    `-oyyo-`                     +oyo+           -oyy/:                                                                 `-:yydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhyo:.                                                                      /+yss``               `/oys+:-:://+oo+.      `:syy:-`````````.:oyo/`   `:oys+.`     `:::---.`     `.ssy+:          -/syy-.      `````                                                         .-oyhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhhhy/-                                                                         .:syy:-                :+ssossssssssoo-`     `-sss++/////////++sso/`   ./syo/      `.yyyssys+.    -/yys-`          +oyso``      :+so/-.``                                                        -:yyhhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdhs:-                                                      `.`                   .osyo:                -:yss+/::---..``      `-sssssssssssssssssso/`   .+sy+:       `--:+oys/.   `/oys+.         ``ssy+/      `-oyssyss+/:-.                                                       .:oyhhhyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdhh/:`                                                    `.-/+s+-`                 `:oys+.`              .-yys/-               `-oss+/----------/oys/`  `-osy/:          `+syo:`  `-oyyo-`         .:yys/-     `-osy++:+osssso//-.`                                                    `-:yhdhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdhy/-                                                    `-:+ossyso:`                  `+sys/.              `.ssy+:          ```  `-oyy/-         `-oys+`   -osyo+-`       .:osyo-`  ./syo/`          :+yso-      :/yys-.```-:+osyyy+:                                                       .:shdhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhddo/`                                                  ``:/ossyso+/.``                    :+yso-`               osyo/``.--::://s+:` `-syh/-         `-shy+.`  `.+ssso++//:::/+ssss/`  `-ssy/-         `.+sys/.    ``ssyo+       `../+o:.                                                         `/oddhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhho/                                                   .:osssso+:-`                         .-yyy/-        ``.`   /+yssoosssyyyysyo:`  ./++-.         `.:/:-`     .-oosssyyyyysssoo.`   `-ssy+:`        ./oys+.`    :/yys+/.``                                                                        :+hhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhdyo:`                                                    ./syso/.`        ``                    osyo+  ``..-:/+so-` :/ysooo++//:---..`                                `.----:://+/:.`      ./oyso/--..`` `-osy+:    `.+sssossso/:-``                                                                     `-oydhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdys..                                              ``       `/oyso-`     .-:+o.`                  /+yso::/+osssssso:` `.-..`````                                                 ```          `/+ssyssoo+/:/+ssy:-    .+syo+-:/ssssso+-`                                                                     `.osdhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdho-.                                             `.:+o.`      ..ssy+/``.:/osyyy:-                  .:ssssssss++/:-..`                                                                           ``-:/++sssssyyyso``   -/syy:- ``.-:+oyo-`                                                                       `-+ydhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdd+:`                                               .:yyyo/`       /+yso++oyyss+:-`                    .+os/:-..``                                                                                     ```..-////:..    `osyo+`       ``-.                     ``::://-.                                             `:/ddhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdo/                                                   `-osyso-`      .osssso+/.`        ``                                                                                                                              -/yyso+-.``                           `-osyyyyyyso:.                                             -+hhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhs:`                                                      -:ssyo+``    `.+sss+.        `..+/:`                                                                                                                            .-++osssso/:..`                      -:ssyo+///osyss/:.                                           `-shhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhh:.                                           ..`            :/ssy/:`     -/yss/-   ``-:/osyso-`                                                                                                                              `.::+ossyso/:-`                 `.ssyso..`  -:+sssso:.                                           .-yhdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydhh/-                                           -:so+.           `-ossso/.    ``osyso..-/+ssyss/:.`                                                ```..---::::::::::::::::::::---..````                                              `.-:/osyss``              `.+oyss:.       `-:ssyoo                                             ./yhdyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhdhs-`                                            :/yys+/        .-oossssss+/     .:sssossyyo+:-.`                                       ``..-::/++oosssyyhhhhhhhhhhhhhhhhhhhhhhhhhhyysssoo++/::-..``                                       `.-/::               .:osys+.`           /+yss``                                            `.oydhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhh.`                                        `       /+yyy/-   `-+ssyso:-/+syy:-    .:syss+:-``                                 ```..-:/+oyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyoo+::..```                                                ``ossss-.             /+yss.`                                              ``yhdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhh/-                                        `./::`    `-+syso:--osyss/:.` `-osyso-`   `:/:-`                               ``..::++ssyhhhhhhhhhhhhhhhhhhhhhyyyyyysssoooooooooossssyyyyyhhhhhhhhhhhhhhhhhhhhhyyso+/:..```                                         -/yys/-             -:ssyo+       ```                                        ./yhdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhs-`                                       ``/oyss.`     `/ossossso+:-      `./oyys/-                                  `..-//osyhhhhhhhhhhhhhhhyyssooo///::-----....`````````````...-----::://+oossyyhhhhhhhhhhhhhhhyss+/-..`                                     :+yso-            `-osyso-`     `./++.`                                       `.oydhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhh-.                                       `./+yys/:``      `.oosss+:`          `.oos/-                              .-:/+yyhhhhhhhhhhhhyyss++/::-..````                                         ```..-:::++osyyhhhhhhhhhhhhyy++:-.                                 -/yys/-          -/ssyo/.`    ``/+syyo/.`                                       .-hhdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhd+:                                        -/osyo/.`           .-osyo+-`           ...`                          `.:/+yyhhhhhhhhdhhyyo+/:-..```                                                             ``..--/+osyhhddhhhhhhhyyo+:.`                            `.ssyss/:`     .-ssyso.`    `./osssooyss/-                                        -/hhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdy/.                                       -:sssss+:`             `.+oyss:.                                   ``.:/syhhhhhhhhdhyss+/-.```                                                                            ```.-//ssyhdhhhhhhhhyy+/-``                         .-+sssso/---:+oyso:.     -/sssso-.`/oyss/-`                                      `:sdhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdd+:                                      `./ossooosso+-`             ..ssyso.`                              `.-+oyhhhhhhhhhhss+/-.```                                                                                       ``.-:/oshhhhhhhhhhyso--``                       `-:ossssssssss+.`   `-:sssoo-.   `./osyo/-`                                      -/dhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdyo                                      ``+oyss:-.:/ssyoo.`             -/syyo/`                         ``-:oyhdhhhhhhhyy+/:.`                              `````      ``.....``      `...`````                                   `.-/+syhhhhhhhdhyo/:``                       .-/oooo+++-.   `./osssso+``      -:ssyo+                                        +sdhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydhs-`                                      ``oos/-     :/ssys+-`            `:+o:-                       `.:+shhdhhhhhhho+:.`                         ```     `.-----..     `.-:::--.`    `.--:----``     ``..`````                        `.:/+yhhhhhhdhhso:..                       `..``     `.+oyss++osyo/.`      -:o/:                                         -ohdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhdho.`                                         `..`       `-+oyys+/`            ``                       -:syhhhhhhhhys+:-``                        `.-----.`    `.-:-----``    `.-:----.`    ``--:----.`    ``------..`                         `--/syhhhhhhhhys:-`                            `-+oyys+:``./oyyo:-       .`                                           `+ydhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhh-.                                                        `.+oyss+:`                                -/oyhhhhhhhhs+/..`                  ``...`    ``--:----``    `.-------``    `.-------``   ``.------.`    ``--:---..       ```                  `./+syhhhhhhhys/:                         -/ssys+-`    `.+oyyy-.                                                   `.hhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                            `-/osss+-.                          ``/+hhhhhhhhyy/:.``                    ..-----.`    ``-------.`    ``-------``    `.------.`     `.------.`     ..-----..`    `....``                 `.-:syhhhhhhhh+/.`                     .:ooo-.        `-+oo.`                                                     :+dhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydhs-                                                               `-/ssyss.`                     ``:+yhdhhhhdys+-`                ``..``   ``.------.     ``-------.     `.------..     `.------.`     ..------.`     ..------``     --:--..``                `./sydhhhhdhy+/``                   ``..            ```                                                        .ohdyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhdyo`                                                                   -/s++``                   `.+sddhhhhhho+.`                  .-:::--`     `.------.`     `.----:-.`     ..----:..`    `.------.`     `.------.`     `-------``    `.--::---``                ``/ohhhhhhdds+-`                                                                                             `+sdhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhd-.                                                                     `.``                  ``/+yhdhhhhho+-`              ``     `.------..     `.------.`     ..------.`     ..------``     .------.``    `.------.`    ``-------.`    ``-------``                 `-/+hhhhhdhho+``                                                                                           `.hhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                                           `./ohhhhhdhy+:``             `.----.`    `..-----..     `.------.`     `.------.`    ``-------``    `.-------``    `.-------`    ``.------.     ``------..                   `:/shdhhhhds+-`                                                                                           :odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydhy-`                                                                                          :+yhhhhhhdo/.`                ..:-----.`     .-------``    `.------.`     `-------.`    ``-------.`    ``------.`     `.------.`     ..------.`    `..:----..                   `./+hhhhhhhh+/                                                                                          `-ohdyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds.`                                                                                         :/dhhhydhh+:`            ``.`    ``-------``    `.-:-----``    `.-:-----      `.-:----.     ``--:----.     `.------..     `.------.`     ..:-----.`     .-------`      `             `-/yhdhhhdd+:                                                                                          `ohhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydd/-                                                                                        `-shdhhhhdo/`            ``--::-.`     `-----:-.`    ``-------``    ``-------`     `.----:-.`     ..----:-.      .-----:..     `.-----:.`     `.------.`     `-------``    ```.             /odhhhhdhy:.                                                                                        -:ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdyy`                                                                                       ..yydhhhhho/                `.----:--`     `-----:-.     ``------..     `.------.`     `.------.`     ..------.`     .----:--``     .----:-.``   ``-------.`    ``-------.`    `...`            /+hhhhhdhy-.                                                                                        sydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+-                                                                                      `:oydhhhdyo:`           ..`     `.------.`     `.------.`    `..-----..`    `.------.`     `.------.`     `-------``    `.-------``    `.-------``   ``.------.`    ``------..     `.-..`          `-oydhhhhhs:`                                                                                      ./hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydho.                                                                                      :+hhhhhdys..          `.----..     `.------.`     `.------.`     ..------``    `.-------``    `--:----.`    ``-------.`    ``-------`     ``------.`     `.------.`    `..-----..`    `.--.`          `.oydhhhhho/                                                                                      .+ydyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhho`                                                                                     -/ddhhhdho-`            `.-::---..     `.-:----.`     `.------.`    ``-------``    `.-:-----``    `.-:-----      `--:----.     ``--:---..     `.------..     `.------.`     ..:-----.`     .-:-.          `-+hdhhhdd+:                                                                                     `oyhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd/-                                                                                    .+ydhhhdd+:          ```     ..------``     .-:--:--`      -----:-.`    ``-----:-.`    ``-------``    `.----:--`     `.---::-.`     ..----:-.      .-----:..     `.-----:.`     `.------.`    `.-:-.`          -/ddhhhdho-`                                                                                   .-ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdyy                                                                                     /odhhhhdo+          `.---``    `.-------``    `.-------`     `.------.`    ``-------.     `.-----:..     `.-----.``     `....---``     ..---:--``     .----:--``    `-------.`    ``-------.`    ``---.`          /ohhhhhds+``                                                                                   sydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+:                                                                                   -/dhhhhhy/.           --:----.`    ``-------``    `.------.`     `.------.`     ..---::-.`    `..-....                           ```      `..------``    `.-------``    `.------.`    ``.------.`    `.--:.`         .:shhhhhd+:`                                                                                  -/dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydhs-                                                                                  ./ydhhhhd/-             ``.------.`    ``------..     `.------..     `.------.`     ..----..`                ```.......-----.......```         ````..-..`    ``-------`     `.------.`     `.------.`    `..:--``        .:hhhhhdy+.                                                                                  .ohdyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy-`                                                                                 :+dhhhhdo/          -.`     `.:-----.`    `..:-----.     `.-:----.`     `.-:::::``     ```        ``...-::////+++ooooooooooooooooooo+++///::-..``     `````     ``--:---..     `.------.`     `.------.`     .-:--`         :+dhhhhdo/                                                                                 `.sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds/                                                                                 .-dhhhhdy/.        `.-::-..     `.------.`     ..------.`     ..------``     ...``        ````--://++ooossssssssssssssssssssosssssssssssssssssssooo//:--``           ..-----..     `.-:----.`     `.------.`     `----`        `:sdhhhdd:-                                                                                 :+ddyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd-.                                                                               `.oyhhhhd+:          `.------.`     `.------.`    ``-------``    `.----:--``        ```..-:://+oosssssssooooooooooooooooooooooooooooooooooooooooooosssssso++/:-```       ..------``     .----:-.``    `------.`     ``---.`        :/dhhhhhs-`                                                                               ..hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdys                                                                                `-shdhhdyo`        `    `.------.``   ``-------.`    ``-------.`    ``---..`    ``.-:/++oosssssssoooooooooooooooooooooooooooooooooooooooooooooooooooooooooosssss++:-.``    `.----:--``    `.-----.`      `````         ``---``        +sdhhdhy:`                                                                                osdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+:                                                                                +sdhydhs-`       `.-``    `.-------``    `.-------`    ``-------.     ```   `.-:++osssssssoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosssso+/-.`   ``.-----.`      ```                          ..:-`        -ohdyhdyo``                                                                              -/dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhy-`                                                                              -/ddhhhho.`      `.--:--.`    ``------.`     `.------.`     `.------.`     ``:/oosssssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooossssso+:-`   ``````````````````              `````       ..:.`       `+ydhhdd+:                                                                               -shdyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy/.                                                                             `-shhhhhh-.        ``-------.     ``------..     `.------..     `.-::-.`   `:+ossssoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosssss+:.`  ``.......`````````..-:::////++++ooso:` ``    ``.-``      `.hhhhhds:`                                                                             `:ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho`                                                                             `-shdhhdo/             ..-----..`    `.------.`     `.------.`    ``.-    `/+sssoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosssso+/-..``..---..`````..-::/++ooooooo+++/////::/:.` ..-`    ``-.`       :odhhhhy:`                                                                             `+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyddo:                                                                              /odhydhs-       `..`     ..------``     .------.`     `-------.`        `:/sssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooossso+/:`````:::--.````.-/++ossso++/::--...`````          ..:--`    ``.``      -ohdyhds+                                                                              -/ddyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd.`                                                                            ..dhhhhho.`      .-::-.`     `-------``    `.----:--``    `.----:--`    `./syooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooossso+-.```--//:-.```.-/oossso+::-.`            ```          ..::--.`     ``       `oydhhdd:.                                                                            ``hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdys                                                                             `+sdhhdd/-        .-:----.`    ``-------.`    ``-------.`    `.---::``   :+ysooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooossso+:.` .:/+/:-``..:+osssoo:-.``       ````     `.---...`     `.------..             .-ddhhdyo`                                                                             osdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                            ./ydhhdys`         ``.-------`    ``.---::-.`    ``------..     `.---   ..sssoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss+/.` .-+++:-```-:osyss+/-.`     ..`   `.-:-.`    `.-::---..     `.------.`             +sdhydy+.                                                                            -+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh:.                                                                           `:yhhhhh/.      ``     `.------.`     ..--.--.`     `..---::..     ```   :+ssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosso+:.``./oo/:.` :/+ssss+:-``    `.:/oo+.`  `..::-.`     ..------``    `.------.`           `:yhhhhh/.                                                                           `:yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdho-                                                                            +sdhydy+.      ..:..     `.---::-.`                    ```..--.``      `-oyooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooossso.` `.oos:-```:+sssss:-`     `./+sssssso+`    .-:--``    `.-------``    `.-------`          `:ydyhdso                                                                            .+ydyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy:`                                 ```                                      `.dhhhhy+`      `---::..     `.-..``      ````......```````     ```      .ossooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooso+.` `:oy+: ``-/ssysso:.     `-/osssoooooosso/`   `.-::-.`    ``-------.`    ``-------``         /sdhhdd-.                                        ```                               `-sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho`                                 .o//                                      /oddydd:-        .-------``          `.-:/++ooooooooooo++//:--..``      -/ysoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss/-  `/+y+/  `:/sssss/:`     .-oosssoooooooooosso:.   `.:::-.`    ``-------.     `.------..        .-dhhhds+                                       .o//                                `+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdo/                                 -/yso``                                  `.shhhdyy`          `.----:--        ..+ossssssssssssssssssssssooo+//``   +ossoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooss+/```:/yo:` `-+ossss+/`    `.:osssoooooooooooooossoo.`   ..:::-.`     ..-----..`    `.------..        sydhhds-`                                    -/yss``                               :+ddyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydd/-                               ``ssssy:-                                  .ohdyhd+:      `.`    ``--:-.      `-+ssssooooooooooooooooooooossssso`` `.ssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooyo/.` .+sy:- ``oosssyo/.`    `++sssoooooooooooooooooosso/.   `.-:---.`     ..------.`     .-------`      -+dhhdhs-                                  ``osssy:-                               .:ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh.`                               :+ssosso/`                                `:hhhhhy-      ..:-.     ``.`      .:sssoooooooooooooooooooooooooosy/:   :/ysoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosy+:`  /oh+:  `/ossosy/:      :+ssooooooooooooooooooooooosso-`   .-::---.`    ``-------``    `.-:--:-.      -shdhhd/.                                 :/ysosss+`                              ``hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyy                               `:oyoooooyo/.                               +sdhydy:`      ----:-.`            oossoooooooooooooooooooooooooosso-  `.+soooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooossoo   :/hs/. `-ossosyo+     `-osyoooooooooooooooooooooooooossoo`    --:--:-.`     `-----:-.`    ``-----`     `-sdhhdyo                               `-oyoooooys/.                               sydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds+                 `  ```````...-:sssooooosss:.`                           ``yhhhhy+`       ..------.`        .-ysooooooooooooooooooooooooooooyo:`  ./oyooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosso-` `:sh+:  `+oysosso-`   ``+ossooooooooooooooooooooooooooooosy+:    `.----:--`     `.----:-.`    ``-::..      +sdhhhh.`               `  ```````...-:sssooooosss:.`                             /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+:                `-:////+++++oooosooooooooosoo/::--...`````               -:ddydd+:          `.--::-.        :+ssoooooooooooooooooooooooooosso/`  `-sssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss+.` -/yss`` -:ysosss+.`    :/ysoooooooooooooooooooooooooooooooooso:`    `.------.`     `.------.`    `.-:-`     -/ddydd/-              `-:////+++++oooosooooooooosoo/::--...`````                 -/dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh/.                `.+oyssssssssssoooooooooooosssssssooo+++/:`              /oddhhh.`     ```   ``--:.`      `.+soooooooooooooooooooooooooooosy:-   /+ysooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss-. `.ssy/- `-+sssosy:.    `:oyooooooooooooooooooooooooooooooooooosss:-     `.------.`     ..------.`     ...`    ``yhhhds+              `.+oyssssssssssoooooooooooossssssoooo+++/:`                `:hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy-                   `.ooysooooooooooooooooooooooooossssyy+/.`             .ohhhdyo      `---`    ``.`       `:oyoooooooooooooooooooooooooossoo`  ``ossooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosy+/  `/oys+. `-ossosso+     .:sssoooooooooooooooooooooooooooooooooooossso``     `.-:----.`     `.------.`    ````     +sdhhds.`              ``ooysoooooooooooooooooooooooooosssyyo/.`                 -ohdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdh+.                     `.+ssooooooooooooooooooooooooosss/:`               ./ydhhd+:     ..:----``            .+ssoooooooooooooooooooooooooosy/-   ``sssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooss+.  .+sys+`  /+ysosss:`   ``ossooooooooooooooooooooooooooooooooooooooooss+:        .-:-----`      -------.`           -/dhhdy+.                `.+osooooooooooooooooooooooooosss/:`                    ./ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy/.                       `:/ssooooooooooooooooooooosso/-`                 -shdhhy:`     `.------..           .ossooooooooooooooooooooooooosso-      +ossooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss/`` :+yyy:- `.sssooyo:`    -/ysooooooooooooooooooooooooooooooooooooooooooos+-`       `.-------``    `.------.`          -yhdhhy-                   -/ssooooooooooooooooooooosso+-`                      `:ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds-`                         `/+ssoooooooooooooooooss+/.`                  `:hhhdho.        `.------.`         -ossoooooooooooooooooooooooooyo-`      :/ysoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss-. ..sssso`` :+ssosso/`   `.+sssoooooooooooooooooooooooooooooooooooooooooooss+.         `.------.`     `.------.`        .+hdhhh/.                    /+ssoooooooooooooooooss++.`                        `.shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho`                           .:ssooooooooooooooooso/.`                    :+dhhdy/`          `.----:-`        .ossooooooooooooooooooooooosso/`  ...` `-ossooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooss+/  `/ssss/- `-oyooosy:.    .+ssoooooooooooooooooooooooooooooooooooooooooooooosy/-   ```    `.------..     `.------.`      `-sdhhdo/                    .:ysooooooooooooooooso/.`                            +yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds+                            :+ysooooooooooooooooyo/.                     osdhhho.`    ...`    `.--:-`        .+ssooooooooooooooooooooooosy-.   //:`  `:oyooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosso-`  `/oys+. `-ossoosoo`    -/ysooooooooooooooooooooooooooooooooooooooooooooooosso+   `.-.`    `.-:----..     `.-:----.`     `+yhhdys                    :+ysooooooooooooooooyo/.                             /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                            oossoooooosssoooooooso+.                   ``yhhhds+     `.-:-.`    ``:-.`       ./oyooooooooooooooooooooosso+   `.+++-`   :+ssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss+.` `` ``::.` /+ysoosy/-    `oossoooooooooooooooooooooooooooooooooooooooooooooooooss.`   ---.`     .-------``     .------`     /oddhhh.`                  +ossoooooosssoooooooss+.                             :/ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd/:                          `.sssoosssssooossssooosso.                   ..hhhdd+:     `-:----.     ``.`       `-oyoooooooooooooooooooooss/-   -:+++//   ``osssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosy:- ``/:-.```   :/+oosso-`   .-ysooooooooooooooooooooooooooooooooooooooooooooooooooosy:-   .-:--``    `.-------``    `.--:-`     -/ddhhd-.                ``sssoosssssooosssoooosso-`                            -:ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd:-                          .:yssssso+/:...:/ssssssss:`                  -:ddhdd:-      ..------.`              `/ossooooooooooooooooooss+.  `.:+///++-.   .:sssoooooooooooooooooooooooooooooooooooooooooooooooooooooooosso+   -://:::--.`````..:--`    :+ssoooooooooooooooooooooooooooooooooooooooooooooooooooss+:   ..:----.`    ``------.``    `.:-.`    .-dhhdd/:                .-ysssssoo/:...:/ssssssss:.                            .-dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd-.                          :+yso+/-.`       --/oossy/-                  :+ddhhh.`        ..------.`             -:ysoooooooooooooooooos+.`  .:/+/////+/-`  `-+sssooooooooooooooooooooooooooooooooooooooooooooooooooooosss:.  `:/::::://:::--..`````  `.+sooooooooooooooooooooooooooooooooooooooooooooooooooooosso/`   `-------.`    `.------..     `.-`    ``hhhdds/                :+yso+/-.`       .-/+ossy/.                            `.hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh.`                         `:/:-.`              ../+y/:                  /sdhhhy``          `.------``       ``  ``sssooooooooooooooosso/   .-+/////////+:.   .-osssoooooooooooooooooooooooooooooooooooooooooooooooooooyo:` `  `.-:://:::::::::::::.` ./oyooooooooooooooooooooooooooooooooooooooooooooooooooooosso/`     ..------.`    `..-----..`    ```     sydhhy+                :/:-.`              ../+s/:                            ``hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh``                          ``                    ``...                 `+yhhdyo     `.-``    `-------.`      ``   :/ysooooooooooooooss/-   :/+//////////++-.   .:ossooooooooooooooooooooooooooooooooooooooooooooooooss+:  `/:-` ``.--:::::::::://:` `-ossooooooooooooooooooooooooooooooooooooooooooooooooooooosso/`       ..------.`     ..------.`          +sdhhho`               ``                    ``...                              yyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyy`                                                                       .ohhhds+     `.:--`     `.------.     ```  `-+ssooooooooooooosy:-   :/+////////////+/-`   `/+sssoooooooooooooooooooooooooooooooooooooooooooosss.` .-++//:.`  ```.---::::/:.` -/ssoooooooooooooooooooooooooooooooooooooooooooooooooooooosso/   ``     `.------``    `.-------``        /odhhds-`                                                                       oydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdys                                                                       `-sdhhdo/     .-::--.`     `.----:``     `   `.+sssooooooooooosy/-   :/+//////////////+:-    `/+sssooooooooooooooooooooooooooooooooooooooooosy/:  `:+///++-. `...````````.`   +ossooooooooooooooooooooooooooooooooooooooooooooooooooooooss/-   ..-`    ``-------.`    ``-------``      :+dhhdy:`                                                                       osdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdso                                                                       `:ydhhd+:     `.------.`     `.-::..           .-sssooooooooooss+:   -:+///////////////++:-`   `-+osssoooooooooooooooooooooooooooooooooooooss+.  .//+//+//  `/oss+//:-.`    ``sssooooooooooooooooooooooooooooooooooooooooooooooooooooooosy-.   --:--`     `.----:-.`    ``------..     -+dhhdy+.                                                                       +sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds+                                                                       ./ydhhd+-       `.------..     `.-:-`            :/sssoooooooosso/`  `-+/////////////////+//-.   `./+sssooooooooooooooooooooooooooooooooosss+.` -:+////+-. `:oysssssys/.    -:ysooooooooooooooooooooooooooooooooooooooooooooooooooooooosss``   ------.`     `.------.`     ..-----`    ./hhhdy+.                                                                       /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds+                                                                       .+ydhhh/.         `.-------``    `---`            .:osssooooooooyo-`  `:/+////////////////////-.`  ``-/osssoooooooooooooooooooooooooooooosy:- `.++///+/:` `-ossooooos+.`    :+ssooooooooooooooooooooooooooooooooooooooooooooooooooooooss+/     `.------..     `.------.`     ..-::``   `:yhhdho.                                                                       /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo+                                                                       .ohdhhh:`    ```    `.-------``   ``-..             -:sssoooooooss+.   `:+////////////////////+/:-.   `./+sssoooooooooooooooooooooooooossoo`  -/+////+:`  :+ysoooosso/    `.+soooooooooooooooooooooooooooooooooooooooooooooooooooooooosy/:       `.------.`     `.------.`    ``-:``    -yhhdho.                                                                       /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                       .ohdhhy:`   ``:-.`    ``------.`     `.``             :/yssoooooosy/-   .-++////////////////////+++-.    `./+ssssooooooooooooooooooooosss:. `-/+///++:- ``sssooooosy:-    `:oyooooooooooooooooooooooooooooooooooooooooooooooooooooooosss-`  ```     .-:--:-.`     `--:----.`    ````    -shddhs-                                                                       :odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                       .ohdhhy-    ``:::-.     ``-----:..     `               `-+sssooooosss``   :/+//////////////////////++/--`    .:+osssooooooooooooooooooyo:` `-+//////+`` :/ysooooosss.`    .ossoooooooooooooooooooooooooooooooooooooooooooooooooooooooss/.  `.-:``    `.----:--``    `.----:--           -shddhs-                                                                       :+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                       .ohdhhy-    ``:---:-.`    `..----:..`                    `.oosssooosso/`   .:/+//////////////////////+++::.``   .-++sssssooooooooooosso/   //+////+:- `.+ssooooosso+     .:ssooooooooooooooooooooooooooooooooooooooooooooooooooooooooyo-`  `--:--.`    ``-------`     `.------.`        -shddhs-                                                                       :+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                       .ohdhhy-      ..------.`     ..------``                    `-/osssoooss+.`  `-:++///////////////////////+///:.`   ``-:/ossssooooooosss.` .-++//////.` .+ssoooooosy/:     :+ysoooooooooooooooooooooooooooooooooooooooooooooooooooooosso/`   `-------.     `.------..     `.------.`      -shddhs-                                                                       :+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                       .ohdhhy-        `-------``    `.-------``                    `-:ssssooss+/   ``:/+/////////////////////////////:-.    `.-/+ossssoosy+:  `://////+/-` .:ssoooooosso-`     +ossooooooooooooooooooooooooooooooooooooooooooooooooooooooss/:     `..:----..`    `.-:----..     `.------``    -shddhs-                                                                       :+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                       .ohdhhy-         ``--:----.`    ``-------`                      .-osysooss+:   `.:/+////////////////////////////++:-.`   ```:/+ssyso-` `:/+//////:   +ossooooooyo/.    `.sssooooooooooooooooooooooooooooooooooooooooooooooooooooooosy-.        ..:-----.`     .-:-----`      --:::``    -shddhs-                                                                       :+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo/                                                                       .ohdhhy-     ```    `.-:--:-.      `-----:-.                       -:ssssoyso-`   ../++////////////////////////////++//-.`     ../:.` -:+/////++.. .-ysooooooooyo-` `  -:ysoooooooooooooooooooooooooooooooooooooooooooooooooooooossoo   `...     `-------``    ``-------``    `.-:``    -shddhs-                                                                       :odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo+                                                                       .ohdhhh:`   ``--`     `.------.`     ..-----..`                      .-+osssyo+``   ..........-://+////////////////////+//:-.```    `.++/////+//   /ossoooooosso/   `  :+ssoooooooooooooooooooooooooooooooooooooooooooooooooooooosy+:   .-:-.``   ``-------.`    ``-------.`    `.``    -yhhdhs-                                                                       /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds+                                                                       .+ydhhh/.     ---.`     ..------.`     ..------``                      `-:/:---``           `````--///////////////////////++///-.  `-/+//////+-. `-oyoooooooosy/- ``  `/sssooooooooooooooooooooooooooooooooooooooooooooooooooooosss:.   .------`    ``-------.`    `.------..           -yhhdho.                                                                       /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds+                                                                       .+ydhhd+-     --:--.`     `.------.`    ``-------``            ```             ```.......   .-:-`  .-//+////////////////////+++-` `-/+/////+//. `-ossooooooooss.` `. `-oyoooooooooooooooooooooooooooooooooooooooooosssssssssssssys+.    `.---::-.`     `.------.`    `..-----..`       .:hhhdho.                                                                       /odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdso                                                                       ./ydhhd+:     .-:----.`     `--:----.`    ``--:----.`           ```      `.-::/++oooooooo/: ``o/-`   `:/++//////////////////+/.` .-+///////+/.` :/ysooooooossoo ``.. ./oyooooooooooooooooooooooooooooooooossssssssssoooooooooossyo-`      ``..-:::..     `.------.`     ..------``     -/dhhdy+.                                                                       +sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdys                                                                       `-sdhhdo/      `.-------``    `.-:-----`     `.------.            ```-:/+ossssssssosssosss+`  +:.` -.```:/+///////////////++.. ``+///////++:- ``sssoooooooosy+: `-```-sssoooooooooooooooooooooooooosssssso++///:-.....````````....   `````     ``.--..     `.------.`    ``----:--     :+dhhdy/.                                                                       +sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyy                                                                       `.ohhhds+        ``-------``    `.------.`     ..---::..      ``--/+osssssooooooooooooosy/- `./- ``so/.` .:/+////////////+::  `-/+///////++`` -/ysoooooooooss:. `-` `./++oosssssssssssssssssssssssoo+/:--.```` ```````````````````    ````        ``...``    `-------.`    ``--:-.     /odhhds-`                                                                       osdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy``                                                                      `+yhhdso          `.------.`     `.------.`     ..-..``   `.-/+sssssooooooooooooooossso+```-/.` -:yys+/`` -:++////////+//.` `:/+///////+/:  `+sssooooooooss+. ..-`    ``..-::://++oooooooooo+//::..`    `````..----:::::::::---...``                 ```      `.------.`    `.-..     +sdhhho.`                                                                       syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh.`                                                                       +sdhhyy       `    `.------..     `.------.`     `    `-:+ossssooooooooooooooooossso/.``.:--   .-ossss/:`  ::+/////++/:`  -:+//////////-` ./oyooooooooooyo:` --.` ...````      ``.....---..``    ``..--://////////::----......`````                            `.------.`    ```     sydhhy+`                                                                      ``yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd-.                                                                       /oddhhh.`    `-.`    `.-------``    `-----:-.`      :/oossssooooooooooooooooosssso-. `.::-` ...` .:ossso+. ``...--::/.` `.++////////+/-` .:sssoooooooooss+`` :- ``+++//::-..`              `.--:////////::--...```       ``````````````                          `.------.`        ``yhhdds/                                                                       `.hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd:.                                                                       -:ddhhd-.    `.-:``    `.-------``    `.--..    `./+ssssooooooooooooooooooossso/-. `-::.` ../++:- ``/osss+/       ``` `.:////////////:`  +ossoooooooooss+: .-:- `.::---..```       ``.---:://:::--..`````      `..--::://++++++ooo++++++/::--.`                    `.------.`      ..dhhdd+:                                                                       .-dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd/-                                                                       .-dhhdd+:     `-:--.`    ``-------``    ``    `:/osssooooooooooooooooooossso+-.```:::.```-:////++.. `.ossss:-   ``   `.//+////////++.. .-ssooooooooooosy-. -/-.   `````     ``..---------..`````````````..::///+ooosssssssssssssssssssssssssso++/:..``               `.----:-`     -:ddhdd:.                                                                       .:ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhddo:                                                                       ``hhhhds/     `.-----.     `.------..       `-/sssooooooooooooooooosssss+/:.``.-::-. ``-:+///////+:.` -/ssss+.` `  ``//+/////////+//   /ossoooooooooosss`` :/`` ````...`````.....````    ```````--:://+ooosssssssoooooooooooooooooooooooooooosssssoo+/:```      ```    `.--:-`     :oddhhh.`                                                                       -/ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds/                                                                         oydhhho`     `.----:-.`    `..-----`     ./oysoooooooooooooosssssoo/:- ``..:::.```.:/++////////+++-` `:ossso-`   -:++//////////+-. `:oyoooooooooooss++ `-//   ````````      `````...--::/+++oosssssssssoooooooooooooooooooooooooooooooooooooooooossssoo/-.``   ````    `.-.      +yhhhyy                                                                         /oddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo`                                                                        /odhhdy:`      ..------.`     .-:.`     :/yssooooooooossssssoo+::.. `.--::-.` `.::/++////////////+//`` -/ssso+   -/++////////+/:` `-sssoooooooooooss:. .+:-  `````.....---::///++ooooossssssssooooooooooooooooooooooooooooooooooooooooooooooooooooooosssss+/-`   `..     ``    `.shhhds+                                                                         +shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds-`                                                                       ./hhhdho.        `.------.`    ``  ```  +oyssssssssssoo++/:-..```---::-..` ..-////////////////////++:- ``oosss:- ``/////////+/:`  +ossooooooooooosso. `-o-` .+++++oooooooosssssssssssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosssss:.  `-:-.`        .+ydhhd+-                                                                       `.ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy:`                                                                        -yhdhhy-`        ``-------.`      :::` .-/+++++///::-..````..---:-.````.--/////////////////////////+/-` .:ssss+.` -:+/////++.. .-ssoooooooooooooyo:` :/+. .:ysssssssssssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss-`  `-:---``      -shdhhy:`                                                                       `-sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy+.                                                                        .+ydhhd+-           `-------.     -/o:-```````````````.------.`````..::/++////////////////////////////.` ./oyss+. `./////+/:  `/ossooooooooooooss+.` o+:` /+ysoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooys+.   `----:-.     ./hhhdho.                                                                        ./ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdhs-                                                                        `.sdhhds+      ``     `.----:-.`   `.:::----.......---..``  ``..::/+++///////////////////////////////+//   /ossy/- `-/+///.` ./syoooooooooooooss+: ``s/`  oossooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss+.`    ``--:.`     /odhhds-`                                                                        .ohdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh:.                                           ``..-.`                       +sdhhhh``    `...     `.---:--`   `  ``````````     --.` .-/++++////////////////////////////////////++.. .-ssso+  `:++/.` -/ssoooooooooooooosy-. -/o/ ``sssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss:.   `    ``.`     `yyhhhy+`                        `..                                             `-yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+-                                     ``.--:/+osy+/                       -/ddydd/-     `-:..     `.--::.` `-`` `--------:-   ooo- `.//+///////////////////////////////////////+/:`  +osss.` -::- ``ossoooooooooooooosoo`  +s:- -/yysooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss/:   ``-.`          -:ddhdd+:                       .:ooo``      ``                                   ./dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo+                                `.-::/+ossyssossso`                      `.hhhhds+      ..:-.`     `--:-. `--. .//+/////+/.` /+h/- `-/+////////////////////////////////////////+/-` -/yyy/- ```` :+ssoooooooooooooosy+/ `-oy`` -:ooosssssssoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooosss+-`   `.-:--.`        /oddhhd-.                      `osyso``   `./++//:.`                              :+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdys                          `.-:++ossyyssso+/-.`-osy..                       osdhydy-`     `--:--`     `--:  `-- `./+/////+/:` `:yo+   ://///////////////////////////////////////+//.  -osyo/    `:oyooooooooooooooosss:` .ooo     ``..-:/++ossysssssooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooossyoo.`     `------:``    `.sdhhdys                       .-yyo-`     +oyyyyyyso.`                            osdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh``                        ./shsso++-:/sy/-    `-sh+:                       ./dhhdhs-      ..:----`     `..` -:`` -/+//////+-` `:sh-. ..++////////////////////////////////////////+:. `:oys+.` :/ysooooooooooooooooys/. .:h+:  ```          `.-::/+oossssssssoooooooooooooooooooooooooooooooooooooooooooooooooooooooosss+:`           --:--      .ohdhhd+-                       :+ho-`    .:yyo:.-:+syo/`                           yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd:-                        `.+ss+.`   `+ss+`    `+ss+`                       -shdhhd+-      `------..     `  ..:. .-++/////+/:  `+yo/`  //+///////////////////////////////////////+/:  `+syo:-.sssooooooooooooooooss+.` /oy:`   `.---..```         `..-:://+ooosssssssssoooooooooooooooooooooooooooooooooooooooosssss+/.`   ``---``    `.``     ./hhhdhy-                       `+ss+`    `/oyo:`  `.+syo.`                        .-dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyddo/                          :+yo.`    :+hs:`    :+hs:`                      `-sdhhdys       `..-----..       `:-```/////////+`` -/hs:` .-+//////////////////////////////////////////   :ossooossooooooooooooooooss+: ``ys+.      ..-::---.`              ``..--::/++ooossssssssssoooooooooooooooooooooooooossssss+/-`      --::---`            +sdhydy:`                       `/syo:.``./syo/`   `.+yo/`                         :+ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+`                         .:hs/.    .-hy+.    .-so:`                        +sdhhhd-.        .-------``     ---` -:+/////++-. `.yss:. `-/+//////////////////////////////////////++.` -/sssssooooooooooooooooooss.. -:hs:` ``     `.-:----.`     `....```       ```..--://++ooosssssssssssssssssssssssssssoo+/--``        `.------..        `.hhhhhy+`                         -:yyso+/+syy-.    ./sh/-                          +sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy-`                        ``sss:`   ``:-.`     ````                         .-ddhhds/         `.-------``   `./. `.+///////+:`  /oho+   -:++////////////////////////////////////++.` -/ysooooooooooooooooooosso+  `/ss+`  ---`     `--:----.`    ``--:----.`           `````..-::///++++ooooooooooo++//:-.```   ``.--.     `.-:--::``      /oddydm/-                            :/ossyyy+/     .:syy..                        `.sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdho.                          /oho/               ``.-:                         osdhydy:`         ``-------.`  `::: `-/+/////+/:` `-syy/-  `//+///////////////////////////////////++.` -/ysoooooooooooooooooooss:. `:sh/:  `--:--``    `.-------`     `.-:--:-.      ``..``             ``````````````           `.::----.`     .-:--      `-sdhhdys                         ```    ``.--.`    `.syyo/                          ./ydyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy:`                         -:o:-             `.++syy-.                       ./hhhhhy:`          ``------..  `:/`` -/+//////+-. `:oyso:` `-//+///////////////////////////////////`  :+ssooooooooooooooooooss+. `-osy.. ``-------``    `.------.`     `.------.`    `..-----..`       ```  `         ```...`     `-------``    `.``     `-shdhhd+-                       `.so/.              `.o+/.`                          -shdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+:                                           :+yys+/.`                        .+ydyhds+             ..---::.` -::- `.++/////+/:  `/osss/- `.:/+///////////////////////////////+/:  `/sssoooooooooooooooooos+.` :+yo+     `.------..     `.------.`     ..------.`     ..--:---``     ..---...``     .-:::--.`    ``-------.`           /odhhdho.                        -/hy+.                ``                            -/dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdyo                                `..``    `-oys+.`                            `oyhhhhd-.             ..--:-. ``+/.` :/+/////++.. .-sssss-. `.//+/////////////////////////////+:. `-oyooooooooooooooooooss+: ``ssy/-       `.------..     `.------.`     `.------``     `-------``    `.-:::---``   ``.-------`    ``.------.        `.hhhhhho.`                      `.+ys+`    `.-.`                                      +sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh``                          `.:/+os+/:.. ./sy+:                               -:ddhhdy+`       ``    ``--:-.` ::+. `-+////////:`  /+ysss+-` `.:/+/////////////////////////+//. `-ossooooooooooooooooosss.` -/yyo-  `-.`     .-------``    `.-:----.`    ``--:----.`    ``-------.`    ``-------`     `.------.`     `.-::--        /sdhydd/-                        ./sh:-    `:sy/:     `-:``                           `yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydd+:                          .:yyoo+ssyso++oss.`                                 osdhydho.      ``.``   ``--:.` .+/: `./+/////+/:. `.+sssss+/```.-//+////////////////////++/:`  /+ssooooooooooooooooosy+/ `.+syo-` ..:--.`    ``-------``    `.-------``    `.-------`     `.------.     ``------..     `.------..     ..-``      ./ydyhdys`                        ./yso`    `-oyy-.    ./sh:-                          -:ddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+`                         ``::.``.-/++sssss+/:.````.-``                       `:yhhhhd+:       `..``   ``--- `./o.` -:++/////+:- ``/osssss+:```.-:/+/////////////////+//`` .-sssooooooooooooooooosso-` .+sy+:   .-:----.`    ``-------.`    ``------.``    `.------.`     ..------.`     ..-----..     `.------.`             -/hhhhhh/.                         +oyoo..`` :+ho+     .:yss``                          +sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy:`                                  `.ossoosssoo/++sy:-                        `:ydhhhhh.`      ``-.`     .-`` -:+:```//+/////++.` `.ssssosso:-`` .-/////////////////:.```-+sssooooooooooooooooosso-` -/yss.`   ``.------.     `.------..     `.------.`     ..------.`     ..------.`     .-------``     .------.`         ``yydhhdy/.                         `+osssso+::ooy:.     /oh+/                          `-sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydhs-                                  .-yso:.--/oossso+-.                          :+ddyhds+`       ..-..     .``  +//. `-//+/////+:.` .:ossoosss+/.````.--::::::::--.` `./+sssooooooooooooooooooss/- ``ssy+:  `     `.------.`     ..-----..`    `.------..     `.------.`     `.------``    `.-------``    `.---::.`       `/oddyhdo/                            `.-:/oossssso/-.` `.yss-`                          .ohdyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+-                                 :+hs/.    `.--.```.-`                        ``sydhydyo.        .-:.`     `  `-+/: `.:++///////-. `./ossoossso+/--.`` `````` ``.-:+osssoooooooooooooooooosyo+```:+ys+.  `-..     `.------.`     ..------.`     .-------``    `.------.`    ``-------.`    ``-------``    ``--.`       ./ydhhdhy.`                                 `.-:/oosssoo//+ys:`                          ./hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdyo                             `-:/oss+.`           +oy/-                         .:yhhhhdo+        ``:--`        ./+-. `.//+///////.. `.+ossooossssoo++/::::::/++oosssooooooooooooooooooooss+-` ./syo/`  .-:--.`     `.------.`    ``.------``    `.-------``    `.-------``   ``.------.`    ``-------.     ``        /odhhhhh/.                         `.://``       `..-//oosyyo/`                           +sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh..                          `.syyss:-             :+yso``                        `-shhhhhd:-        `.:--`       `.+/-``.-//+///////-` `./osssooosssssssssssssssssoooooooooooooooooooooss+/`` :+yss-.   .-:----.`     `-------.`    ``-------.`    ``-------``    `.------.`     `.------.`     ..------.`          .-hhhhhdy:`                         `+osyy-.    ``     ```.:/+:.                          `.hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyddo:                            ///-.                ./sy/:                          -/ddhhhhs-`        `-:-.`       .-+::```-:++////++:-```-:ssssooooooooooooooooooooooooooooooooooooosso+`` -:yyy+:      `.-------`     `.------.     ``-------.     `.------..     `.------.`     ..------.`     ..---:-`        `.ohhhhdd+:                          -/yys/:`    :/o/-``      ``                            :/ddyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds.`                                              .-:osyo-`                          +odhhhhh/-        ``--:..       `://:.` ..::////++/:.` .-+osssoooooooooooooooooooooooooooooosssso/. `./oyyo/` ````    ``-------`     `.----:-.`     ..----:-.`     ..----:..`    `.-----:.`     `.------.`     `--``        ./hhdyhdso                          `-oys+.`    .-yyyyyo+:.`                                `ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydho.                                          `.::ossoosss:.                          .ohdhhhhh-.        ``-::.`        :/+/:.` ``..---:::.`   .-/+oooooooooooooooooooooooooooossso+-. `.+oyss:- ``--:-.     `.------..     `.------.`     ..------.`     ..------``    `.------.``   ``-------.`     `        ..yhdhhdhs-`                         `-osy:-     `/oso://syy+/                               .+ydyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd/-                                      ../+osyso/-.:+yo+``                         `/odhhhhhs:`        `..:--``      `.:/o+/:-..`` ````` ```.-:+osoooooooooooooooooooosssss+/-.```-+oyso:-   ``::----.`    `..-----..     `.------.`     `.------.`    ``-------``    `.-------``   ``.-------           `-oydhhhds+`                           :+yso`       .-:. ``:/yss``                            ./hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdys                                   .-/ooyss+/..`  `-oyh/-                          ``osdhhhhh+:          ..:--``      ``-:+ssso+/////////++ooooooooooooooooooooosssssoo+:-`` .-+oyys/:.`      ..------.`     .-------``    `.-:----.`     `-------.`    ``-------.`    ``-------`     `.-::-.          -/hhhhhdys``                            -/yss..             `:syy-.                            +sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd-.                             `.:/ssyss+/.``      `./o..                            `-shdyhhhh:-          `--:-`         `.-ooyyysssssssssssooooooooooossssssssoo+/:```` .-/syyso:-`  `..`    ``-------``    `.-:-----``    `.-:-----      `--:--:-.     ``--:---..     `.------..     ...`         .-hhhhydhy:`                               -osyo+/.`          :+yso``                          `.hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy+`                            .+yhso/-.               `./-.                            /oddhhhdy/.`          --:-.     ````  `.:/++oossssssssssssssssoo++//::..  ```.-:+oyys+/..   ``--::-.`    ``-----:-.`    ``-------``    ``------.`     `.----:-.`     ..----:-.      .-----:..              .:ydhhyhds/`                                  `:/ssyss/:.``   .-yys:.                            +sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydy/.                           `.:/.`               `--osyso``                            +sdhhhhds+``         `.--..`    `...``   `...-------------...`````....-:/++ooo++--.` `    ``.----:--`    ``.------.`    `.------..     `.------..     `.------.`     ..------.`     .------`          ``/odhhhhdso``                                      .-/osyys+/--:+syo:`                           `:ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh/.                                             -:+syso+syo/`                            ./yhhhhhds+.`         ``---.`   ``---..```               ``..--:///////:::-.``  ``..-.`     `.------.`     `.------.`    `..-----..`    `.------..     `.------.`     `-------``    `..-``         `./odhhhhdy+-`                           ``.`````        `.:/osssssss/:                            `:hhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdyo                                          `-/ssyso-..:+hs+.                             -:hhdhhdhy+:           ``.-``    `.-:----.`             ````.....```         ``--:---..     `.------.`     ..------.`     ..------``    `.-:-----`     `--:----.`    ``-------.`                -/yhdhhhhd/-`                           `-+ooo+++///:-.      `.-:+/:--``                            +sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdd:.                                      -:ossoo/.`   ``osyo/                              -/hddyhhhh/:            `..``    `.::----.`     ``......``     ``------.     `.-:----.`     `.-:----.`     `-------``    `.-------``    `.-:-----`     `.-:-----      `--::::.`             -:hhhhyddh+:                             `+oyssosssyyyyss/-`       ``                               .-dhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhho`                                 `./+yysss+/`       `-oyy:-                              `/sddhhhdhs:.            ```     `.------..     ..::::--.`     ..:-----.`     .----:--``     .-:--:--`      -----:-.`    ``-----:-.`    ``-------``    `.------.`     `...`            `-ohdhhhhds+.`                             -:hyo-```..--:/+oyss/:`                                    `+ydhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydho-                               :/syyo+:/+ys/.`     `-oyh/-                                 +ohhhhhdhh/:`                   `.------..     `.------.`     `-------``    `.-------``    `.-------`     `.------.`    ``-------.     `.------..     `.------..                 `-:yhdhhhhhs+``                               .-yss-`        :/sssss+:.``                               .+ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhd+:                              /+y+/.` `.osy+:`` ..+osss..                                  `:oydhhhhhyo:-                    .------.``   ``-------.`    ``-------.`    ``-------``    `.------.`     `.------.`     ..-----..`    `.------..     `.----..               .:oyhhhhhdys/.                                    +oho/      `-+so+:+oyys:.                              -/dhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhh.`                            `.-``     .:sss///+oyso:-                                      `.oshhhhhdhy/:`                  `.-------``    `.-------``   ``.------.     `.------..     `.------..     `.------.`     ..------.`     .------.``    `..`              `-/yhdhhhhdyo-.                                      -/yss.`   -/syy-. `.++o-`                            ``yydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds+                                      -/ssssssss/:.`                                         `.:syddhhhhhy+:                  ``--:::--`     `.------.`     `.:-----.`    `..:----..     `.-:----.`     `.------.`     `-------``    `.---::--                    :/shhhhhddhs/.`                              ``.`       .+sy+:`..ssy/-     ```                              /oddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy+.                                  ..sss/-..-..        ````.-:..                               ``+shhhhhhhhs+:.`                ``..--:-.     `.------..     `.------.`     ..------``     .-------``     .------.`     `-------.`    ``-..``               ``-+ohhhhhhhhso.`                                `+oy+:      `./oysoooso-`                                     ./ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhd+:                                `/oys/.         ``-://+ooosss+:.`                              `.-+shhhhhhhhso-.`                 ``..-..`    `.------.`     `.------.`    ``-------``    `.-------``    `.-------`    ``.----:-.                      `.-oshhhhhhhhso:.`                                   :/ssy/-`     `.++ssss+.``                                   -/hhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhh.`                              `+so/`        `./osssssoo+//ssy/:                                 ``/+yhhhhhhhyyo:-``                  ```      `.----:-.``   ``-------.`    ``-------.`    ``-------``    `.----:-.`     `.-..``                  ``-:+sydhhhhhhyo/``                                       `-+sys/.`      .:+osso/:`                                ``yydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhds+`                              .--.        :/sss+/:--.``  /oho+                                    `.:+yhhhhhhdhho+:..                          `.----:--``   ``.------.`    ``-------.     `.------..     `.------``                         `.:+oyhdhhhhhhh+/-`                                 `          .-ssy:.        `./+yss``                              `/oddyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdho.                                      `-/syyso``       `osy/-                                        -:+sydhhhhhhhys/:.`                        ````.....`     `.--:::--`    `..--:::-.`    `..-----.`     ``````                       `.:/syhhhhhhhdys+:-                                   `-+/:          osy+:``        ``---                               ./ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdo+                                   .:+syoo+sy+:`     .-yso-`                                          ``./+shhhhhhhhhyso::.``                                   ``......``     ..-..-..`      ``````                              ``-:ooyhhhhhhhhhyo+..`                                     -/yyy/:`     -:ssssso+/-.                                        /odhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhd:-                                 /+yso..`/+yso-.   :/hs+.                                              ``.:/syhhhhhhhhhyyo+:-```                                                                                          ```--+osyhhhhhhhhhys/:.``                                        `.:osys+-``.:osso//+sssss+/:.`                                 .-hhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhhs-`                               `-/-.   ``+oyso...+syo-`    ``--:::..`                                     `.:+ohhhhhhhhhdhyso+:-.``                                                                                ```--+osyhdhhhhhhhhhso:.`                                      ``--:.`   .-ssyo+/+sss-. ``-:/ooyso-`                               `.ohhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydhh/.                                         ./osoossy/-     `/+sssyyso/-.                                        -:/oshhhhhhhhddhhyso/:-..``                                                                  ``..-:/osyhhddhhhhhhhhso+:-                                        .-/ossyy/-     .-+syyss:.        `.:-`                                .:yhdyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdhy.`                                        `/oyys/:     `-+syo+:::osyso-.                                         ``-:/osyhhhhhhhhhhhyyyoo+/:--..``                                                ```.--://+oyyyhhhhhhhhhhhhso+:-.`                                         .-osyss/::..       `:/sss/:`                                          ``sydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdy+.`                                    -:osso+-`    ``+oyso.`   .-/osso/.                                            `.-:/ooyyhhhhhhhhhhhhhyyysso+//:::---..```                    ```..---::://+ossyyyhhhhhhhhhhhhhyyoo/:-.`                                            `-osso+-.             ``:/ssy+:`                                       `+sdhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdhs:.                                   +oyso.`     `:+yyo:.       `-:sss:.                                               ```.-:/+ssyyhhhhhhhhhhhhhhhhhhyyyyssooo+++++++++++++++++++oosssyyyhhhhhhhhhhhhhhhhhhhyss+/:-.```                                                .+sh/:`                  `-+syso-`                                   `-ohdhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdys``                                 `./.`      .+sys+.`          `ssy/-                                                      ```.-:++osyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdhhhhyyso+:-.````                                                      -oyy:-                     `.sso-`                                  `oydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhdy+.                                          :+yss.`          `-/yss:.   ``/:.``                                                     ``..-:/++ossyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyysso++/:--.``                                                     `          ./sys+.`             ```     ```                                  `/sddyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydhs:.                                        +oho/           ./oyso-`    :+yys+/..`                                                            ```..--:::::///////+++++++///////::::---..``                                                            `.-/:-..       `:+yys:.          `-o+/`                                       `-ohdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdyy.`                                      /+ho+.`        :+yys:.    `:oyoo+ssso/:-                                                                                                                                                                .-+ossssoo:-.`    ``+oyso.`      ``+oho+                                      `.sydhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdyo.`                                    .:ssyo+-`    -:yyy/:     .:syy--`--ooyss/:`                                                                                                                                                             /+yso::/osyso+/``   `-+syo/.`  `-/oyys:.                                     `+sdhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyydhh/.                                    ``/+yys+/::/osyo:.      osyo+     `.:+syo-`                                                                                                                                                            /oh+:  `.-+oyyy+:     `/oyso///+syyo/.`                                    .:yhdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdd:-                                      -/oyyyyyys:-      :+yso.         :/hs+.    ``..`                                                                                                                                      ``````       /oh+:      `./oys+.     `+osyyyys/:                                      .-hhdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdy/.`                                    `.-::::--      `-oysso:-``    `.+syo:`    :+ys+/:..`                                                                                                                            `..:/+osoo/-.     /oh+:      ``:/yyy/-      `-----.`                                     .:shhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhds+``                                                .:sss++oss+/-...:osy/:    `.+ssssssso+/:.`                                                                                                                    `.-:+osssso+osyso``   /oh+/   `.:/oosoosss.`                                              ``/ohhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhds+.`                                             `+oyo+`..:+sssoossyso``    .+sy+:.--+ossss+-`                                                                                                                `.+ssssoo:-.``.:oyy:-   :+yso..-+oyss+/.-+syo/`                                            `/odhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdhy/-                                           -:hys:.   ``:/yssoo+-.     .:yyy.`   ``.:+yso-`   ..:-.`                                                                                                       `/sys/.`      `:oyo/`  `-ossoosss/:.``  `+syo-`                                         .:yhdhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhh/:`                                        ``/:-`      :+ho+``        +oyo/         `ssy/:   :+yyso+//:.`                                                                                   -:o/.`         .-yyo-`       `/oyo-`  `.+syso-`        `.``                                        `-:hhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdyo:.                                                  /oh+:        .-yys:.        `.sss:.   `.:/sssssy:.  `:/-.                                                               `..`         :+hs/.           osy+/    ``.:oss+.`    .-yss:.                                                  .-+yhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdys-.                                                osh/-        :+ys/.         :/ys+.      -/yso..-.` `-oys+-`        `.-``           `````            ````````.....``    `+os:.        -/hs+.           -/yss...:/oosss/-        /oyso``                                              .-sydhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhy+-`                                             -/o:.      `-oys+.`       `.+sy+.`      +oh+/      `:oysso-       `:sy/-         `:/++//-.        ``//+++ooooosss++.`   osho/        :+ys:`            .+syooossoo/:-`         `-o/:`                                            `./shdhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdys-.                                            ```       .+sy/:         ./sh+:      `.sss:.      .+osssy/-      ./yh:-        :+yssssyoo``      ``sssooo++++/+osyo/`  -/yys/-`   `.oys+`             `-oyss+:-.``              ``                                            `.oshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhy+.`                                                  .:ssyo+/:-``   -/sss.`      -/hs+.      `-sss:/sss``    .osy.`      .-yys/:.:+sy+:       `osy/-```````.+sy+.`  `-+syso:. .+sh/-                :+ys/.                                                             `./shhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhys-.`                                               `.-:/oosss++///osyo/       `/oyo:`      .:yoo`./sh/-   `-sss``     ./oys/.  `.+syo-`      osy/-        /oyo-`    ..+oyso/+sss``                ..ssy/-                                                         `..oshhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdhs+-.                                                  ..://ossssso+-`      `.oys+`       -/ho+  `+ss+.` -/yo+      .+syo/`     -/yso-`     +oh+:       `/oys:`      `./ossss+/                    +oho+                                                       `-/sydhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhdyy:-`                                                    ..-::.`      `..-/sh/:        /oh+/   -:hs+. :+h+/     .:yss.`       `osy+:     /+yo+.....--/oss+-`         .-ssy/-                    .-/-.                                                    `.-sydhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhyo:-                                                              `/osssyy/:.`      +oy/-   ``osy:.:+h+:     .:yso...```````+oy+:     :/ysooossssssss:-             +oy+/                                                                           .:+yhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhhhh+/.`                                                          `-:/++ooss+.    ``sss:`     -/yoooos:.     .:ssoooooooooooosy/:     -/yso++//::/osy-.             -/yso`                                                                      `.:+yhhhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhyo/``                                                          ```..:::`    .-hyo-       .+sssso-`     -/yso////++++++osy/-     .:yss````  .:syo/`            `:sso``                                                                  ``/+yhhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdyo:.`                                                                    `.o+:`       `.+yys+.      -/yso``````````osy/-     `:sss.`     `/oys/.            `...                                                                 `.:osdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyhhdys:-.`                                                                               -:so:`      -/hso         `ssy/-     `-oyy-.      ..ooo-.                                                                             `-:syhddyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhdhhs+:.`                                                                              .``       .-/::         `/++:.      `://.`        `````                                                                         `.:/syhdhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyhhhhhyo/--                                                                                                    ````                                                                                                .-/oshhhhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyy+/-`                                                                                                                                                                                                `./+yyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdhhso-.                                                                                                                                                                                          ..oshhdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhddhy+/.``                                                                                                                                                                              ``./+shhddhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhdddyyo/:..`                                                                                                                                                                    ``.-:osydddhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhhys+/:.`                                                                                                                                                            `.::+sshhhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyhhhhhhhss+/:.`                                                                                                                                                  ``-:+oshhhhhhhyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhhhhyso/:``                                                                                                                                        ``:/+syhhhhhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhddhys+/--```                                                                                                                        ```.-/+syydddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhysoo/:-..```                                                                                                        ```...:/oosyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyoo/::--.```                                                                                        ```..-::/+osyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhhhhhhhyysso++::--..``                                                                      ``..--::/+oosyyhhhhhhhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyss++//:--...```                                            ```...--::/++osyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyyssoo++++/////:::::::::::::::::::://///+++++osssyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
climate/src/ASCII/the-league-bigger.txt000064400000131745147361032630013637 0ustar00hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyyssooo+++////////////+++ooossyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyso+//:-..````                          ````..-://+osyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyo+/:-.``                                                    ``.-:/+osyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyso/:.``                                                                    ``.-/+syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo+:.`                                                                                  `.:/oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhyo+-.`                                                                                            `.-+oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhhyo:.`                                                                                                      `.:oshhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyo:.                                                                                                                .:oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhhys/.`                                                                                                                       .:oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+-`                                                                                                                              `-+yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+-                                                  ``..        -++++-         .---..``                                                 ./yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy/.                                               -//oosssss.     `+yo:/sy:      .ossoossss+-     ..                                           ./yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh+.                                                  /ys:...``      `oyo`  -sy+`    oy+`  ``.:o:    `sy/                                             ./yhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho-                                       `+s/          -sy-          `oyo`    `oy+   -sy-             +yo`      +s:                                       -ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy:`                                          :ys.          oso/+osys`   `sy:`.....:yo`  :ys`   `o+//-   -yy-      :ys.    .:.                                   :ydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds-                                `.            oyo          :ys/:--.`    `ssssssssssso`  +y+    `-:oy/  `oy+      `sy/    -ysys+:.                                 .ohhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh/`                               .:oyy:           .sy:         .sy-         `oy/``    -ys` `oyo`     -sy-  :ys.      +yo`   .sy/`-/osss-                                `/hdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy:                              .-+syo/-`             /ys.         +y+-:/+ooy: `oy/      -ss`  `+ysso+++ss+  `sy/      -sy:   -sy/     `-:`                                   :ydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy:                                /ys/`     `           `oy+ `.-:+s- :yoo+/:--.`                   `---:/+:`    /ys/-.` `oy+   .ssoss/-.                                          -sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy-                            .:`    :ss: `-+sy:           -ysosso+/:.                                             -/oossssss.  .sy/`.:+sy:                                           -sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy:                             `oys-    -ssoss+:`             :/-.`                                                      `.--.`  .sy+`     `            `:++o+-                           -sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh:`                                :sy+`   .ss+`    `.+:                                                                           ./osso:.`             -syo//oso/.                          :ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh/`                         .+:       .oss-   .sy+`./oss+:`                             ``......----......```                          ``-/osso`         `+ys:`  `-+ss/                          `/hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdo.                           -sy+.   ./os+os+`  .ossso/-`                     `..://+ossyyyhhhhhhhhhhhhhhhhyyysso++/:-.`                     `--         :sy+.       /ys.                           `+dhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy-                        `/:`  `+ys:-oss/.``+ys:` `/:``                 `.-/+osyhhhhhhhhhyyyysssoooooooosssyyyyhhhhhhhhhyso+/-.`                         /ys:        :sy+`   ``                        -sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh+`                       `/ss/`    -osso-`     -ss-                  `.:+syhhhhhhhyso+/:-..``````           ``````.-::++oyyhhhhhhyso:.`                    /ys.      .oys-   `:os/`                       `/hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdy.                       `/sy+`       `/ss/`      ``               .:+syhhhhhys+/-.``                                      ``.-/+oyhhhhhys+:.`               `+ss+-```:ss/`  `:oy+/ss/.                       .sdhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhd+`                      `/ss+os+.        -oyo-                  .-+shhhhhyo+:.`                                                    `.-/oyhhhhhs+:.              `:osoooso-  `:oso-` `/ss/.                       /hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhy-                       `oy/` ./ss+.       `/s/`             `./shhhhhs+:.`             `    `...`   `----`   .---.`    ```              `.:+shhhhhs+-`             .::-.` `:osoos/`   `/s/                        .ydyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo`                         ``     ./ss+.       `            `-+yhhhhyo:.           `    .---.   .----`   .----`  `.----`  `.---.`               .-+yhhhhyo-`               `/sso-``/ss/`   `                          `+dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhd/                                    ./ss+.               `.+yhhhhs/.`            `---.`  `----.   `----.   .----`  `.----`  `.---.`  `..``         `./shhhhy+-`            .oo-     .+o.                                :hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh-                                       `/so.            `/yhhhhs/.          .--.`  `.---.`  `----.   `----.   .----`   .----`  `.---.`  `.--..`         .:shhhhy+.                                                        -hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhy.                                          `           `-ohhhhy/.        `.`  `.---.`  `.---.`  `----.   `----.   `----`   .----`  `.---.`  `.---.`          .:shhhhs-`                                                      `sdyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyds`                                                     `:yhhhho-          .----`  `.---.`  `.---.`  `----.   `----.   `----`   .----`  `.---.`  `.---.`           .+yhhhy/`                                                     `odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo`                                                    `/yhhhy/`       .--`   .----`  `.---.`  `.---.`  `----.   `----.   `----`   .----`   .----`  `.---.`   ``      `:shhhh/`                                                     +dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo                                                    `/hhhhs-          .----`   .----`   .----`  `.---.`  `----.   `----.   `----`   .----`   .----`  `.---.`  `..`      -shhhh/`                                                    +dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo                                                    :hhhhs-      `---.   `----`   .----`   .----`  `.---.`  `----.   `----.   `----`   .----`   .----`  `.---.`  `--`      .ohhhh/`                                                   +dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydo                                                   -ydhds-      `  `----.   `----`   .----`   .----`  `.---.`  `.---.`  `----.   `----.   `----`   .----`   .----`  `--.      .sdhhh:                                                   +dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhydo                                                  `odhhy-      .--.   `----.   `----`   .----`   .----`   .---.   `....    `....`   `--:-.   `----`   .----`   .---.`  `-:.      -ydhds.                                                  +dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhyds                                                  /dhhd/        `.---.`  `----.   `----.   `----`   .---.    `    ```.....--....```    ```.-.   `----`   .----`   .----`  `.:.`     :hhhd+                                                  odyhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhyhy`                                                `ydyds`     .:-`  `.---.`  `----.   `----.   `--.`      `.--//+ooosssssssssssssssssoo++/-.`       `----`   .----`   .----`   .:.     `odhdy.                                                 sdyhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhh.                                                :dhhd:       .----`   .---.`  `----.   `--:-`    `.-:/+oossssoooooooooooooooooooooooooooossso+/-`    `----`   `--:-`   .--.`    .:.     -hhhd/                                                `hhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhd:                                                +dyds`    `-`   .----`   .----`  `.---.`  ``  .:+ossssoooooooooooooooooooooooooooooooooooooooooosso+-`  `.---`   ```               .:`    `odydo`                                               -dhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhyd+                                               `ydhd/     `----`   .----`   .----`  `.-:-`  ./ossooooooooooooooooooooooooooooooooooooooooooooooooooosss+.  ``.`..``` ``..---::/+-     .-`    :dhhh.                                               /dyhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhy`                                              .hhhh-    `   `----`   .----`   .----`  `.  .+ssooooooooooooooooooooooooooooooooooooooooooooooooooosso/-`..--..`.-:++ooo++//:-----` .-`  `.`    -hhhh-                                               sdyhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhh.                                              -hhhh.    .:-.   `----`   .----`   .----`  `/ssooooooooooooooooooooooooooooooooooooooooooooooooooss+-`.-/:-`.-/ooo+:-.`      ```     .:--`  ``    `ydhd:                                              `hhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhyd+                                              -dhhy.     `----.   `----.   .----`   .:-` `osooooooooooooooooooooooooooooooooooooooooooooooooosso:`.:+:.`-/oso/-`  `   .-.   `-:--.   `----`       `sdhd:                                              :dhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhy`                                             -hhdy`   `.`  `----.   `...`    ..---`  `` `osooooooooooooooooooooooooooooooooooooooooooooooooss/.`-++-`./oyo:.  `./+so-  `--.   `----.   .----`      `sdhd:                                              sdhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhd:                    .`                       .hhhy`   `-:-.`  `-.`   `.........``  ``   `+soooooooooooooooooooooooooooooooooooooooooooooooss/.`:s/.`:oss+-`  ./ossooos+.  .--.`  `----.   `----`     `sdhd-                        .`                   -dhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhds                    .y+                      `yhhh.     `.---.     `:+osssssssssoo+/:-`  /yooooooooooooooooooooooooooooooooooooooooooooooos+. :s+``:oss+.   -+ssoooooooss/` `-:-.`  `----.   `----`    `yhhh.                      .so`                   odhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhd:                   -sss/                     sdhh-   `-`  `.-`   `/ssoooooooooooooooy+  -sooooooooooooooooooooooooooooooooooooooooooooooss: .ss- -osso-   -osooooooooooooss:  `---.`  `----.   `----`   .hhhy`                    -sss/                   -hhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhy                   :sooos/                   +dhd/    -:--`      `+sooooooooooooooooso` .ssooooooooooooooooooooooooooooooooooooooooooooos+``/y/``+sos/`  .+ssooooooooooooooos+` `.---.`  `----.   `-:-`   -dhdo                   :ssoos/                   sdhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhd/          .--::///+sooooos/-..```           -dhdo      .--:.     /sooooooooooooooooss. `osooooooooooooooooooooooooooooooooooooooooooooss: .oy- -ssss:   :ssooooooooooooooooooss-  `.---.`  `----.   `--    +dhd:         .--::///+sooooos/-..```            -dhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhh`          `/ssssssoooooooossssoooo-        `sdhy`   ..   .-`    -sooooooooooooooooss-  +sooooooooooooooooooooooooooooooooooooooooooooso. :ss. :soss.  `+soooooooooooooooooooooss:   `.---.`  `----.`  ..   `sdhy`        `/ssssssoooooooossssoooo-          `yhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhd+             .+ssooooooooooooooso:`         /dhd:   `:--`        +soooooooooooooooos-  `ssooooooooooooooooooooooooooooooooooooooooooos+``+ys. /soss.  `osoooooooooooooooooooooooos/    `.---.`  `.---.`      -dhd+          ./ssooooooooooooooso/`            /dhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhd:               .+soooooooooooso-`          `yhds     .----`      osooooooooooooooos/    /yoooooooooooooooooooooooooooooooooooooooooos/ `oss. /soss.  `osoooooooooooooooooooooooooos/     `.---.`  `.---.      odhh.           .+soooooooooooso:`              -dhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhy`                -soooooooooos/             /dhd:   `   `--:`     osoooooooooooooos/  -. `+soooooooooooooooooooooooooooooooooooooooss: .sss. :soss-  `osooooooooooooooooooooooooooooy:  .`  `.---.`  `.---.`   .hhd+            -yoooooooooos/                  sdhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhdo                 osoooossoooos+            `yhds   `--.   `-.     /yooooooooooooos+  .++. `+soooooooooooooooooooooooooooooooooooooss. ``./- :yssy:  `osoooooooooooooooooooooooooooooos.  --`  `.---.`  `.---`   odhh.           +soooossoooos+                  +dhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhd:                -yssso+::+ssoso`           -dhd/   `----.   `     `ssoooooooooooso` `///+.  :ssooooooooooooooooooooooooooooooooooso` -/:-.``..:+:   /yooooooooooooooooooooooooooooooos+  .:--`  `.---.`  `.:.   -dhd/          .sssso+::+ssoss`                 -dhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhd-                +o/-.`    .-+oy-           odhh.     `----.        -sooooooooooss. `/+///+:` .+sooooooooooooooooooooooooooooooooso` `::::::::-...` -sooooooooooooooooooooooooooooooooso`  .----`  `.---.`  `.`  `yhds          +o/-.`    `-+oy-                 .hhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhh`                `            `-.          `yhds   `-`  `----.    `  :sooooooooos/  :+///////.  .osooooooooooooooooooooooooooooos+``:-``.-::::::/: `osooooooooooooooooooooooooooooooooso`    .----`   .----`      +dhh`         `            `-.                  yhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhs                                           .hhd/   .:-.`  `----`   `  -ssoooooooy:  :+///////+/.  -ossooooooooooooooooooooooooos/ `/+/+- ````...-` /yooooooooooooooooooooooooooooooooos+  ..   .----`   .----`    :dhd:                                           odhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhdo                                           /dhd:   `.---.`  `-:-       .osoooooos+  -+/////////+/.  ./ssoooooooooooooooooooooos/ `///+: .sso++:   .soooooooooooooooooooooooooooooooooos.  -:-`   .----`   .---.   -dhd+                                           +dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhd+                                           +dhh.     `.---.`  `--        :ssooooos-  :+//////////+/-` `-ossooooooooooooooooooy: .+//+: `osooos.   +sooooooooooooooooooooooooooooooooos/   `----.   `----`   .-:`  `yhdo                                           /dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhd/                                           odhy`  `-`  `.---.`  `.        `+ssoooss.  :+////////////+:.  .:ossoooooooooooooos- .+//+/ `osoooy/   :yoooooooooooooooooooooooooooooooooos.     `----.   `----`   .`   sdds                                           /dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhd/                                           odhy   `:--`   .----`            .+ssooss-  -+/////////////+/:.  `:ossoooooooooss- .+//+/  +soooso`  `osoooooooooooooooooooooooooooooooooy/  .:.   `----.   `----`      sdds                                           :dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhd/                                           odhy    .----`   .----`            `/ssoos+` `:+//////////////+/:.  `-/ossooooss. -+////` +sooooy:   :yoooooooooooooooooooooooooooooooooso`  ----.   `----.   `----`    sdds                                           :dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhd/                                           odhy      .----`   .----`            `:ssoss:  `/+///////////////+//-`  `-+osss. -+///+` /yoooos+   `ssoooooooooooooooooooooooooooooooooy-    `----.   `----.   `--:`   sdds                                           :dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhd/                                           odhy`  `.   .----`   .----`             -+ssss:` `:/::://////////////+/:.`  `-` :+///+. :yooooss. ` /yooooooooooooooooooooooooooooooooos+  .-`  `.---.`  `----.   `-`   sdds                                           :dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhd+                                           +dhh.   --.   `----`   .----`             `:/--`      `.``-/+///////////////. `/+///+. -yoooooy/ ` `ssooooooooooooooooooooooooooooooooss.  ----`  `.---.`  `----.      `yhdo                                           /dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhdo                                           /dhd:   .:--.   `----`   .----`       ``    `.-://++/- .+.  -/+///////////+- `/////+- -ssooooso``. /yoooooooooooooooooooossssssoooooosy-    .--:-`  `.---.`  `.---.    .hhd+                                           +dhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhds                                           -hhd/    `----.   `----`   .----`     `.-/+ossssooooso``+` /. -///////////. .+////+- .ssoooooy- -``ossssssoooooooossssoo/::-..````````.  ```   ``.-`  `.---.`  `.-:.   :dhd:                                           odhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhy`                                          `yhdo      `----.   `----`   .-.`  .-+ossoooooooooss/``/. :ys/``-+/////+:` :+////+: .ssooooos+ .-  ``.-::/++ooooo/:-.`` ``..--:::::---..`          ``   `.---.`  `-.   +dhh.                                           shhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhh.                                           odhh`   .`  `----.   `----`    ./ossoooooooooooss/.`--````:sss: `-:://- `/+////+: `osoooooos. :``/::-.`     `````..-:////:--..```````````                `.---.`     `yhds                                           `hhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhd:                                           :dhd:   .:.`  `----.   `-`  `:ossoooooooooosss+:`.--``-++:``/ss+`  `   -+/////+: `osoooooos/ -: `-..```````.---:---.``````..-://++oooooooo++//:..`         `.----`   -dhd/                                           -dhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhdo                                           `yhds   `---.`  `----.    `/ssooooooooossso/:`.---``-//////. -sss- ` `:+/////+: `osooooooss``/` ``..```...```````..-://+oossssssoooooooooooossssso+:.`   ``   .-:`   odhh.                                           /dhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhy`                                           +dhd-   `.---.`  `-:.   .ssoooooosssoo/:...---``-:////////+: `+ss:  /+/////+: `+sooooooos: /: `````...--::/+++oossssssooooooooooooooooooooooooooossso+:.  ``  `.   .hhdo                                            sdhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhd-                                           `hhdo     `.---.`  ` .. /soooooo//-....---.``-:////////////+/` /ss/``/+///+: `osooooooos+ .o `ooooooosssssssooooooooooooooooooooooooooooooooooooooooooss. `:-`     +dhh.                                           .hhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhd+                                            /dhd-      `.---.`  -+-`......``...-.`..-:///////////////////` /ss+ `///+: `osoooooooss. o: /yoooooooooooooooooooooooooooooooooooooooooooooooooooooooy/  `--:.   .hhdo                                            /dhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhy`                           `.`             `yhhy`   .`  `.---.  ``....`````.. `::///////////////////////+. :sy/ `++- .osooooooooy: :o `ssoooooooooooooooooooooooooooooooooooooooooooooooooooooss/`    .-    sdhh.              ``                            sdhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhd:                    ``.-/+osy/              :dhd+    --`  `.-:` -` :::::/` +s` /+/////////////////////////` /ss- -- -ssoooooooos+ `y- /yssssoooooooooooooooooooooooooooooooooooooooooooooooosso.  .-.      /dhd/              -so`   .-.`                   -dhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhds                .//+oosso/-.+y-              odhd:   `---`  `.- `- .+///+: `so `/////////////////////////+/ `oyo` `:ssooooooooss. oo  `..-:/+ooossssooooooooooooooooooooooooooooooooooooooss+-   `----`   -hhds              -yo.  `+sooso.                 odhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhd-               -ys:.`.yo`  .yo`             `yhhh.   `----`  `` -- :+///+- .y+ `/////////////////////////+- .sy-`osoooooooooos- :y.  ...``   ``.-:/++oosssssooooooooooooooooooooooooooss+:`  ..   `-.   `yhhh`             `oy.  `+y/``-ss.               .hhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhds                /h-   /h/   /h:              .hhhy`    .----`    :. /+///+. :y/ `/+////////////////////////` +soosoooooooooos/ .y/    `----.    ```  ``..:/++oossssssssoooooooossssso+:.    .:--.       sdhd-              `oy+..+y+   -yo`               odhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhd:               `ss.  `/.   `.`               -dhds`     .----`  `/``//////` /y+``:+//////////////////////+. /yoooooooooooos+ `ss` --`  `.---.`  `----.      ``...-:///++++o++++/:-.`  `..`  `.---`    odhd/                `/+oss/   -sy.               .hhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhds`               :s:        `:+o.              :dhds`      .----` ./ `//////` +yo. -//////////////////////+` +soooooooooooso` +y: `---.`  `.---.`  `----.   `....`              ````   .---.`  `-`    +dhd/              `/:    ``   .so.                odhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhd/                         `oy+-`               :dhds`       .--:. -/ `////+/` +ys/``:+//////////////////+: .ssooooooooooss` /y/    .---.`  `.---.`  `----.   `-:--.   `----`   .-:--`   .---.`      odhd/               +y:   `                        :dhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhh.                .:+o/:.`/y/                   :dhdy`    `   .-:. :+ `//////` /sso- `:+///////////////+: `osooooooooooss. /y+` .`  `.---.`  `.---.`  `----.   `----.   `----`   .----`   .-:-    `sdhd/               /y:   :y/   .:`                `yhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhyds                .o:-:ososs:.   .               -hhhh.    `.   `:. -+``/+///+. -ssso:``-/+//////////+/- .osooooooooooss. /yo` .:--`   .----`  `.---.`  `----.   `----.   `----`   .----`   `    .ydhd:               /y+`  -yo`  `sy-                odyhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhyd/                     `ss/+ssosy:               .hhhd:    `-.   .. -o. :+///+: `+soss/. .-////////:.``/ssooooooooooss. /yo`   .----`   .----`  `.---.`  `----.   `----.   `----`   .----`     -hhhd-                /+oso+os.  `oy-                :dhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhd-                   `+h/  `.-.``-               `sdhd+     .:.   ` `+: .////+/. .ososso/-.````` `.:ossooooooooooos+``+y+` -.   .----`   .----`   .----`  `.---.   `----.   `----`   `-.     /dhdy.                    .-/osso/+y:                .hhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhh`                .osy+        +h+                /dhhh.     .:.     :+. -++//+/. -ossoosssooooosssooooooooooooss- .sy/  -:--.   `----`   .----`   .----`  `.---.`  `----.   `----.       .ydyd+                -oy-       .-+s/                `ydhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhyds                 --`          :yo`               .hhhd+     `-:.    `:/. .:/+++/. .+ssoooooooooooooooooooooss: `+ys.    `----.   `----`   .----`   .----`  `.---.`  `----.   `-:-`     /dhhh-               `+y+.   +y+-`                     odyhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhydo                          .:ososs.                +dhhh-     `-:.     :+:. `.--::` `-/osoooooooooooooosss+- .+ys: `-:.   `----.   `----`   .----`   .----`  `.---.`  `----.   `     -ydhdo`               `oy:   `os:/sy/                   +dyhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+                     `-+syo:. `sy-                .ydhds.     `-:.     ./oo+:-----:/+osooooooooosssso/-`.:oyo-   `.---.   `----.   `----.   `----`   .----`   .----`  `.-:-      .odhdy-                 -ys`    ``  .sy.                 /dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+                  :oss+-`     `:`.                 :hhhdo.     `.:.`    `.:+sssssssssssssssooo+::.``-/oso:.``--`  `.---.   `----.   `----.   .----`   .----`  `.---.`  ``     `+hhhh/                    /sso:.    `+y/                 :dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+                 ./.         `:oyo.                `+hhhho.      .--`  `..```..--------..`..---/+++/-.``   .----`  `.---.`  `----.   `----.   `----`   .----`   .-:-`      `+hhhdo`                      `-/oss/-:oy:                 :dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+                         `-+ss/-oy:                 `+hhhho.      `..`  `---..        `..---..```   ----`   .----`  `.---.`  `----.   `----.   `----`   .----`  ``      .+hhhdo.                 ://::-.`   `:+o+/.                 /dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhd+                      ./sso-`  `/yo.                 .+hhhhs-       ``   `----.   `.-...   `.---`   .----`   .----`  `.---.`  `.---.`  `----.   `----`   .--.`       -ohhhho.                 .os//++oss+-`                       /dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdo`                  :sso:+y/`   `oh/                   `+hhhhy/.           `.---.`  `----.   `----.   .----`   .----`  `.---.`  `.---.`  `----.   `----.          `:shhhh+.                   -ys.    `+sss+.`                   +dhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds`                 ./.   -yo:-+ss/`                     `:shhhho-`          `.---.`  `----.   `----`   .----`   .----`  `.---.`  `.---.`  `----.`  `..`       `-ohhhhy/`                      :yo`  .oy/`-oo.                 `odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhy.                     `/s++o+-`      ``                 `-+hhhhyo-`         `.---.`  `----.   `----.   .----`   .----`  `.---.`  `.---.`  `---.          `-+yhhhho-`                  -/`    /yo-:so.   ``                 .ydyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh:                   `oy/`     .:/++oso:`                  `:ohhhhyo:.          `...`  `.---.`  `----.   `----`   .----`   .---.`  `.---.   `         `-+yhhhhs:`                     :ss/`   ./sss-`                     -hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhd+`                  /:     .+so//:..+y/                     `:ohhhhho/.`               `----.`  `----.   `----`   .----`   .---.   ``           `./oyhhhhs/.                         `/ss.    `:+yo`                   /dhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhds.                     `-+sss.    `sy.                        .:oyhhhhyo/.`              ```.`   `----.   `----`   .---.   ``              `.:oyhhhhyo:.                     .o+.    `oy+-``    ..                  `odhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh/                    :yo..+y+.  /y+     ``                     `./oyhhhhys+:.`                      `     ````                      `.:+syhhhhyo/.`                        `:ss+``:so//oso+:`                    :hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdo`                   .    .+s/+y+`  `:+oo+-`                      `./oyhhhhhys+/-.``                                        ``.-/+syhhhhhyo+-`                      `-/+o.  `:osos:`  `.:+-                   `+dhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh:                       `-ss+-   -sy/--/ss/.                        `.-+oyhhhhhhhyo+/:--.````                  ````.--:/+osyhhhhhhys+:.`                        `/ss+-.`    `:os+.                         :yhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds.                     /y+.   `+yo.    `:ss.                            `.-/+osyhhhhhhhhyyysso++++++//++++++ossyyyhhhhhhhhyss+/-.`                             +h/`          `-oy+`                     .odhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhh+`                    .    -ss:       -ss.  `-`                              `.-:/+ossyyhhhhhhhhhhhhhhhhhhhhhhyysso+/:-.`                                    +y+`        `    :-                    `/hhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhy:                        +h/      `+yo.  `oyso:.`                                   ```...-----::::-----....``                                    ./+/-`    :ss:      -s+                        -ydhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds-                      .oso-` `:ss:   .sy-`:oss/`                                                                                              /yo:/sso/.  `+yo.``-+yo.                      .sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds.                      .+ssosy+`   :ys.    `+h/   ``                                                                                  ``     /h:  `./sy:   -sssss+.                      .odhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdo.                      `--.`    /yss/-`  :ss.  `oyo+:.`                                                                        `.:/osso-   /h:   `-+sy/    `..`                      .odhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdo.                           `+y+`.+so+oyo`   +y/.:+sss-                                                                    .osso:.`.oy:  :yo.-oss/./yo`                          .odhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds-                         -s/    -y+-.    /yo`    .sy-  /s/:-.`                                                  .:`      /y+`    .sy-  :sss:.    /:`                        .odhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhdy:                              +h:     :ys`     -ys`  `-ss+o. .o/`                                     -:`     +h/       +h+  `-+ys`   .ss:                              -sdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh+`                           -o.    -ys.     .sy.    +y/    :yso     :y/      :++/.     `/++oooss+.  oh/     +y:        +yooso/-`     `o:`                          `/hdhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhds-                               .sy+-`   .sy-    :yo     os+s+    +y-    `oy/:sy:    `ss:....:ss. `/ys:` /h+         `oy/                                      -ohhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyhhy+.                             .-/ossoosy:    `sy-    .yo -h+  .so`   -yy-  `oy/    +y-     oy-   `:ss+so`          `sy:                                  `/ydhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy:`                               .-:.    ../y/     /h/  :h+ :h/   .ss.    `oy:   /y+...-/ss.      -sy-            .:.                               `:shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs:`                                   `/ooso/    oy.   /y/+y-   .so+/////oy:   -yooo++ss.        :yo                                           `-ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs:`                                    `-.   -y+     /yso    -yo--::::oy-   .ss`   -ys.       `+/`                                       `-ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs/.                                        `       :s:    -yo     `sy-   `oy-    -o+`                                              .:shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhy+-`                                                   `.`      ..`    ``                                                   `-+shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs/-`                                                                                                                `-/syhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhys/-`                                                                                                        `-/oyhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhs+:.`                                                                                              `.:+syhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhys+:.`                                                                                    `.:+oyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyso/:.``                                                                      ``.:/osyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyo+/:..``                                                      ``..-/+osyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyss++/:-..````                               ```..-:/++ssyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhyyssso+++////::::::::::::////+++oossyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
climate/src/ASCII/fancy-bender.txt000064400000001014147361032630012700 0ustar00<blue>     ( )</blue>
<blue>      H</blue>
<blue>      H</blue>
<blue>     _H_</blue>
<blue>  .-'-.-'-.</blue>
<blue> /         \</blue>
<blue>|           |</blue>
<blue>|   .-------'._</blue>
<blue>|  /<white>/  '.' '.</white> \</blue>
<blue>|  \<white>\ <black><blink>@   @</blink></black> /</white> /</blue>
<blue>|   '---------'</blue>
<blue>|    _______|</blue>
<blue>|  .'<black>-+-+-+</black>|</blue>
<blue>|  '.<black>-+-+-+</black>|</blue>
<blue>|    """""" |</blue>
<blue>'-.__   __.-'</blue>
<blue>     """</blue>climate/src/ASCII/bender.txt000064400000000327147361032630011610 0ustar00     ( )
      H
      H
     _H_
  .-'-.-'-.
 /         \
|           |
|   .-------'._
|  / /  '.' '. \
|  \ \ @   @ / /
|   '---------'
|    _______|
|  .'-+-+-+|
|  '.-+-+-+|
|    """""" |
'-.__   __.-'
     """climate/src/ASCII/404.txt000064400000000177147361032630010663 0ustar00  _  _    ___  _  _
 | || |  / _ \| || |
 | || |_| | | | || |_
 |__   _| | | |__   _|
    | | | |_| |  | |
    |_|  \___/   |_|climate/src/Decorator/Component/Format.php000064400000003116147361032630014604 0ustar00<?php

namespace League\CLImate\Decorator\Component;

class Format extends BaseDecorator
{
    /**
     * The available formatting options
     *
     * @var array
     */
    protected $formats = [];

    /**
     * An array of default formats
     *
     * @var array $defaults
     */
    protected $defaults = [
            'bold'          => 1,
            'dim'           => 2,
            'underline'     => 4,
            'blink'         => 5,
            'invert'        => 7,
            'hidden'        => 8,
        ];

    /**
     * Add a format into the mix
     *
     * @param string $key
     * @param mixed  $value
     */
    public function add($key, $value)
    {
        $this->formats[$key] = (int) $value;
    }

    /**
     * Retrieve all of the available formats
     *
     * @return array
     */
    public function all()
    {
        return $this->formats;
    }

    /**
     * Get the code for the format
     *
     * @param  string  $val
     *
     * @return string
     */
    public function get($val)
    {
        // If we already have the code, just return that
        if (is_numeric($val)) {
            return $val;
        }

        if (array_key_exists($val, $this->formats)) {
            return $this->formats[$val];
        }

        return null;
    }

    /**
     * Set the current format
     *
     * @param  string $val
     *
     * @return boolean
     */
    public function set($val)
    {
        $code = $this->get($val);

        if ($code) {
            $this->current[] = $code;

            return true;
        }

        return false;
    }
}
climate/src/Decorator/Component/DecoratorInterface.php000064400000000603147361032630017115 0ustar00<?php

namespace League\CLImate\Decorator\Component;

interface DecoratorInterface
{
    public function add($key, $value);

    /**
     * @return void
     */

    public function defaults();

    public function get($val);

    public function set($val);

    public function all();

    public function current();

    /**
     * @return void
     */

    public function reset();
}
climate/src/Decorator/Component/Color.php000064400000003676147361032630014445 0ustar00<?php

namespace League\CLImate\Decorator\Component;

class Color extends BaseDecorator
{
    /**
     * The available colors
     *
     * @var array
     */
    protected $colors = [];

    /**
     * An array of default colors
     *
     * @var array $defaults
     */
    protected $defaults = [
            'default'       => 39,
            'black'         => 30,
            'red'           => 31,
            'green'         => 32,
            'yellow'        => 33,
            'blue'          => 34,
            'magenta'       => 35,
            'cyan'          => 36,
            'light_gray'    => 37,
            'dark_gray'     => 90,
            'light_red'     => 91,
            'light_green'   => 92,
            'light_yellow'  => 93,
            'light_blue'    => 94,
            'light_magenta' => 95,
            'light_cyan'    => 96,
            'white'         => 97,
        ];

    /**
     * Add a color into the mix
     *
     * @param string  $key
     * @param integer $value
     */
    public function add($key, $value)
    {
        $this->colors[$key] = (int) $value;
    }

    /**
     * Retrieve all of available colors
     *
     * @return array
     */
    public function all()
    {
        return $this->colors;
    }

    /**
     * Get the code for the color
     *
     * @param  string  $val
     *
     * @return string
     */
    public function get($val)
    {
        // If we already have the code, just return that
        if (is_numeric($val)) {
            return $val;
        }

        if (array_key_exists($val, $this->colors)) {
            return $this->colors[$val];
        }

        return null;
    }

    /**
     * Set the current color
     *
     * @param  string   $val
     *
     * @return boolean
     */
    public function set($val)
    {
        $code = $this->get($val);

        if ($code) {
            $this->current = [$code];

            return true;
        }

        return false;
    }
}
climate/src/Decorator/Component/BackgroundColor.php000064400000002425147361032630016434 0ustar00<?php

namespace League\CLImate\Decorator\Component;

class BackgroundColor extends Color
{
    /**
     * The difference to add to a foreground color code
     * to get a background color code
     *
     * @const integer ADD
     */
    const ADD = 10;

    /**
     * Get the code for the requested color
     *
     * @param  mixed $val
     *
     * @return mixed
     */
    public function get($val)
    {
        $color = parent::get($this->strip($val));

        if ($color) {
            $color += self::ADD;
        }

        return $color;
    }

    /**
     * Set the current background color
     *
     * @param  mixed   $val
     *
     * @return boolean
     */
    public function set($val)
    {
        return parent::set($this->strip($val));
    }

    /**
     * Get all of the available background colors
     *
     * @return array
     */
    public function all()
    {
        $colors = [];

        foreach ($this->colors as $color => $code) {
            $colors['background_' . $color] = $code + self::ADD;
        }

        return $colors;
    }

    /**
     * Strip the color of any prefixes
     *
     * @param  string $val
     *
     * @return string
     */
    protected function strip($val)
    {
        return str_replace('background_', '', $val);
    }
}
climate/src/Decorator/Component/BaseDecorator.php000064400000001712147361032630016071 0ustar00<?php

namespace League\CLImate\Decorator\Component;

abstract class BaseDecorator implements DecoratorInterface
{
    /**
     * An array of defaults for the decorator
     *
     * @var array $defaults;
     */
    protected $defaults = [];

    /**
     * An array of currently set codes for the decorator
     *
     * @var array $current;
     */
    protected $current  = [];

    public function __construct()
    {
        $this->defaults();
    }

    /**
     * Load up the defaults for this decorator
     */
    public function defaults()
    {
        foreach ($this->defaults as $name => $code) {
            $this->add($name, $code);
        }
    }

    /**
     * Reset the currently set decorator
     */
    public function reset()
    {
        $this->current = [];
    }

    /**
     * Retrieve the currently set codes for the decorator
     *
     * @return array
     */
    public function current()
    {
        return $this->current;
    }
}
climate/src/Decorator/Component/Command.php000064400000003005147361032630014727 0ustar00<?php

namespace League\CLImate\Decorator\Component;

class Command extends BaseDecorator
{
    /**
     * Commands that correspond to a color in the $colors property
     *
     * @var array
     */
    public $commands = [];

    /**
     * The default commands available
     *
     * @var array $defaults
     */
    protected $defaults = [
            'info'    => 'green',
            'comment' => 'yellow',
            'whisper' => 'light_gray',
            'shout'   => 'red',
            'error'   => 'light_red',
        ];

    /**
     * Add a command into the mix
     *
     * @param string $key
     * @param mixed  $value
     */
    public function add($key, $value)
    {
        $this->commands[$key] = $value;
    }

    /**
     * Retrieve all of the available commands
     *
     * @return array
     */
    public function all()
    {
        return $this->commands;
    }

    /**
     * Get the style that corresponds to the command
     *
     * @param  string  $val
     *
     * @return string
     */
    public function get($val)
    {
        if (array_key_exists($val, $this->commands)) {
            return $this->commands[$val];
        }

        return null;
    }

    /**
     * Set the currently used command
     *
     * @param  string       $val
     *
     * @return string|false
     */
    public function set($val)
    {
        // Return the code because it is a string corresponding
        // to a property in another class
        return ($code = $this->get($val)) ? $code : false;
    }
}
climate/src/Decorator/Parser/Parser.php000064400000001341147361032630014100 0ustar00<?php

namespace League\CLImate\Decorator\Parser;

use League\CLImate\Decorator\Tags;

abstract class Parser
{
    /**
     * An array of the currently applied codes
     *
     * @var array $current;
     */
    protected $current = [];

    /**
     * An array of the tags that should be searched for
     * and their corresponding replacements
     *
     * @var \League\CLImate\Decorator\Tags $tags
     */
    public $tags;

    public function __construct(array $current, Tags $tags)
    {
        $this->current = $current;
        $this->tags    = $tags;
    }

    /**
     * Wrap the string in the current style
     *
     * @param  string $str
     *
     * @return string
     */
    abstract public function apply($str);
}
climate/src/Decorator/Parser/Ansi.php000064400000007557147361032630013555 0ustar00<?php

namespace League\CLImate\Decorator\Parser;

use League\CLImate\Util\Helper;

class Ansi extends Parser
{
    /**
     * Wrap the string in the current style
     *
     * @param  string $str
     *
     * @return string
     */

    public function apply($str)
    {
        return $this->start() . $this->parse($str) . $this->end();
    }

    /**
     * Get the string that begins the style
     *
     * @param string $codes
     * @return string
     */
    protected function start($codes = null)
    {
        $codes = $codes ?: $this->currentCode();
        $codes = $this->codeStr($codes);

        return $this->wrapCodes($codes);
    }

    /**
     * Get the string that ends the style
     *
     * @param string|array $codes
     * @return string
     */
    protected function end($codes = null)
    {
        if (empty($codes)) {
            $codes = [0];
        } else {
            $codes = Helper::toArray($codes);

            // Reset everything back to normal up front
            array_unshift($codes, 0);
        }

        return $this->wrapCodes($this->codeStr($codes));
    }

    /**
     * Wrap the code string in the full escaped sequence
     *
     * @param  string $codes
     *
     * @return string
     */

    protected function wrapCodes($codes)
    {
        return "\e[{$codes}m";
    }

    /**
     * Parse the string for tags and replace them with their codes
     *
     * @param  string $str
     *
     * @return string
     */

    protected function parse($str)
    {
        $count = preg_match_all($this->tags->regex(), $str, $matches);

        // If we didn't find anything, return the string right back
        if (!$count || !is_array($matches)) {
            return $str;
        }

        // All we want is the array of actual strings matched
        $matches = reset($matches);

        return $this->parseTags($str, $matches);
    }

    /**
     * Parse the given string for the tags and replace them with the appropriate codes
     *
     * @param string $str
     * @param array $tags
     *
     * @return string
     */

    protected function parseTags($str, $tags)
    {
        // Let's keep a history of styles applied
        $history = ($this->currentCode()) ? [$this->currentCode()] : [];

        foreach ($tags as $tag) {
            $str = $this->replaceTag($str, $tag, $history);
        }

        return $str;
    }

    /**
     * Replace the tag in the str
     *
     * @param string $str
     * @param string $tag
     * @param array $history
     *
     * @return string
     */

    protected function replaceTag($str, $tag, &$history)
    {
        // We will be replacing tags one at a time, can't pass this by reference
        $replace_count = 1;

        if (strpos($tag, '/')) {
            // We are closing out the tag, pop off the last element and get the codes that are left
            array_pop($history);
            $replace = $this->end($history);
        } else {
            // We are starting a new tag, add it onto the history and replace with correct color code
            $history[] = $this->tags->value($tag);
            $replace = $this->start($this->tags->value($tag));
        }

        return str_replace($tag, $replace, $str, $replace_count);
    }

    /**
     * Stringify the codes
     *
     * @param  mixed  $codes
     *
     * @return string
     */

    protected function codeStr($codes)
    {
        // If we get something that is already a code string, just pass it back
        if (!is_array($codes) && strpos($codes, ';')) {
            return $codes;
        }

        $codes = Helper::toArray($codes);

        // Sort for the sake of consistency and testability
        sort($codes);

        return implode(';', $codes);
    }

    /**
     * Retrieve the current style code
     *
     * @return string
     */

    protected function currentCode()
    {
        return $this->codeStr($this->current);
    }
}
climate/src/Decorator/Parser/ParserFactory.php000064400000001073147361032630015432 0ustar00<?php

namespace League\CLImate\Decorator\Parser;

use League\CLImate\Util\System\System;
use League\CLImate\Decorator\Tags;

class ParserFactory
{
    /**
     * Get an instance of the appropriate Parser class
     *
     * @param System $system
     * @param array $current
     * @param Tags $tags
     * @return Parser
     */
    public static function getInstance(System $system, array $current, Tags $tags)
    {
        if ($system->hasAnsiSupport()) {
            return new Ansi($current, $tags);
        }

        return new NonAnsi($current, $tags);
    }
}
climate/src/Decorator/Parser/NonAnsi.php000064400000000461147361032630014213 0ustar00<?php

namespace League\CLImate\Decorator\Parser;

class NonAnsi extends Parser
{
    /**
     * Strip the string of any tags
     *
     * @param  string $str
     *
     * @return string
     */

    public function apply($str)
    {
        return preg_replace($this->tags->regex(), '', $str);
    }
}
climate/src/Decorator/Parser/ParserImporter.php000064400000000671147361032630015627 0ustar00<?php

namespace League\CLImate\Decorator\Parser;

trait ParserImporter
{
    /**
     * An instance of the Parser class
     *
     * @var \League\CLImate\Decorator\Parser\Parser $parser
     */
    protected $parser;

    /**
     * Import the parser and set the property
     *
     * @param \League\CLImate\Decorator\Parser\Parser $parser
     */
    public function parser(Parser $parser)
    {
        $this->parser = $parser;
    }
}
climate/src/Decorator/Style.php000064400000014637147361032630012524 0ustar00<?php

namespace League\CLImate\Decorator;

use League\CLImate\Decorator\Parser\ParserFactory;
use League\CLImate\Util\Helper;
use League\CLImate\Util\System\System;

/**
 * @method void addColor(string $color, integer $code)
 * @method void addFormat(string $format, integer $code)
 * @method void addCommand(string $command, mixed $style)
 */
class Style
{
    /**
     * An array of Decorator objects
     *
     * @var Component\DecoratorInterface[] $style
     */
    protected $style = [];

    /**
     * An array of the available Decorators
     * and their corresponding class names
     *
     * @var array $available
     */
    protected $available = [
        'format'     =>  'Format',
        'color'      =>  'Color',
        'background' =>  'BackgroundColor',
        'command'    =>  'Command',
    ];

    protected $parser;

    /**
     * An array of the current styles applied
     *
     * @var array $current
     */
    protected $current = [];

    public function __construct()
    {
        foreach ($this->available as $key => $class) {
            $class = 'League\CLImate\Decorator\Component\\' . $class;
            $this->style[$key] = new $class();
        }
    }

    /**
     * Get all of the styles available
     *
     * @return array
     */
    public function all()
    {
        $all = [];

        foreach ($this->style as $style) {
            $all = array_merge($all, $this->convertToCodes($style->all()));
        }

        return $all;
    }

    /**
     * Attempt to get the corresponding code for the style
     *
     * @param  mixed $key
     *
     * @return mixed
     */
    public function get($key)
    {
        foreach ($this->style as $style) {
            if ($code = $style->get($key)) {
                return $code;
            }
        }

        return false;
    }

    /**
     * Attempt to set some aspect of the styling,
     * return true if attempt was successful
     *
     * @param  string   $key
     *
     * @return boolean
     */
    public function set($key)
    {
        foreach ($this->style as $style) {
            if ($code = $style->set($key)) {
                return $this->validateCode($code);
            }
        }

        return false;
    }

    /**
     * Reset the current styles applied
     *
     */
    public function reset()
    {
        foreach ($this->style as $style) {
            $style->reset();
        }
    }

    /**
     * Get a new instance of the Parser class based on the current settings
     *
     * @param \League\CLImate\Util\System\System $system
     *
     * @return \League\CLImate\Decorator\Parser\Parser
     */
    public function parser(System $system)
    {
        return ParserFactory::getInstance($system, $this->current(), new Tags($this->all()));
    }

    /**
     * Compile an array of the current codes
     *
     * @return array
     */
    public function current()
    {
        $full_current = [];

        foreach ($this->style as $style) {
            $full_current = array_merge($full_current, Helper::toArray($style->current()));
        }

        $full_current = array_filter($full_current);

        return array_values($full_current);
    }

    /**
     * Make sure that the code is an integer, if not let's try and get it there
     *
     * @param mixed $code
     *
     * @return boolean
     */
    protected function validateCode($code)
    {
        if (is_integer($code)) {
            return true;
        }

        // Plug it back in and see what we get
        if (is_string($code)) {
            return $this->set($code);
        }

        if (is_array($code)) {
            return $this->validateCodeArray($code);
        }

        return false;
    }

    /**
     * Validate an array of codes
     *
     * @param array $codes
     *
     * @return boolean
     */
    protected function validateCodeArray(array $codes)
    {
        // Loop through it and add each of the properties
        $adds = [];

        foreach ($codes as $code) {
            $adds[] = $this->set($code);
        }

        // If any of them came back true, we're good to go
        return in_array(true, $adds);
    }

    /**
     * Convert the array of codes to integers
     *
     * @param array $codes
     * @return array
     */
    protected function convertToCodes(array $codes)
    {
        foreach ($codes as $key => $code) {
            if (is_int($code)) {
                continue;
            }

            $codes[$key] = $this->getCode($code);
        }

        return $codes;
    }

    /**
     * Retrieve the integers from the mixed code input
     *
     * @param string|array $code
     *
     * @return integer|array
     */
    protected function getCode($code)
    {
        if (is_array($code)) {
            return $this->getCodeArray($code);
        }

        return $this->get($code);
    }

    /**
     * Retrieve an array of integers from the array of codes
     *
     * @param array $codes
     *
     * @return array
     */
    protected function getCodeArray(array $codes)
    {
        foreach ($codes as $key => $code) {
            $codes[$key] = $this->get($code);
        }

        return $codes;
    }

    /**
     * Parse the add method for the style they are trying to add
     *
     * @param string $method
     *
     * @return string
     */
    protected function parseAddMethod($method)
    {
        return strtolower(substr($method, 3, strlen($method)));
    }

    /**
     * Add a custom style
     *
     * @param string $style
     * @param string $key
     * @param string $value
     */
    protected function add($style, $key, $value)
    {
        $this->style[$style]->add($key, $value);

        // If we are adding a color, make sure it gets added
        // as a background color too
        if ($style == 'color') {
            $this->style['background']->add($key, $value);
        }
    }

    /**
     * Magic Methods
     *
     * List of possible magic methods are at the top of this class
     *
     * @param string $requested_method
     * @param array  $arguments
     */
    public function __call($requested_method, $arguments)
    {
        // The only methods we are concerned about are 'add' methods
        if (substr($requested_method, 0, 3) != 'add') {
            return false;
        }

        $style = $this->parseAddMethod($requested_method);

        if (array_key_exists($style, $this->style)) {
            list($key, $value) = $arguments;
            $this->add($style, $key, $value);
        }
    }
}
climate/src/Decorator/Tags.php000064400000002555147361032630012316 0ustar00<?php

namespace League\CLImate\Decorator;

class Tags
{
    /**
     * Original keys passed in to build tags
     *
     * @var array $tags
     */

    protected $keys = [];

    /**
     * Available tags and their values
     *
     * @var array $tags
     */

    protected $tags = [];

    public function __construct(array $keys)
    {
        $this->keys = $keys;
        $this->build();
    }

    /**
     * Get all available tags
     *
     * @return array
     */

    public function all()
    {
        return $this->tags;
    }

    /**
     * Get the value of the requested tag
     *
     * @param string $key
     *
     * @return string|null
     */

    public function value($key)
    {
        return (array_key_exists($key, $this->tags)) ? $this->tags[$key] : null;
    }

    /**
     * Get the regular expression that can be used to parse the string for tags
     *
     * @return string
     */

    public function regex()
    {
        return '(<(?:(?:(?:\\\)*\/)*(?:' . implode('|', array_keys($this->keys)) . '))>)';
    }

    /**
     * Build the search and replace for all of the various style tags
     */

    protected function build()
    {
        foreach ($this->keys as $tag => $code) {
            $this->tags["<{$tag}>"]    = $code;
            $this->tags["</{$tag}>"]   = $code;
            $this->tags["<\\/{$tag}>"] = $code;
        }
    }
}
climate/src/CLImate.php000064400000031233147361032630010747 0ustar00<?php

namespace League\CLImate;

use League\CLImate\Argument\Manager as ArgumentManager;
use League\CLImate\Decorator\Style;
use League\CLImate\Settings\Manager as SettingsManager;
use League\CLImate\TerminalObject\Dynamic\Spinner;
use League\CLImate\TerminalObject\Router\Router;
use League\CLImate\Util\Helper;
use League\CLImate\Util\Output;
use League\CLImate\Util\UtilFactory;

/**
 * @method CLImate black(string $str = null)
 * @method CLImate red(string $str = null)
 * @method CLImate green(string $str = null)
 * @method CLImate yellow(string $str = null)
 * @method CLImate blue(string $str = null)
 * @method CLImate magenta(string $str = null)
 * @method CLImate cyan(string $str = null)
 * @method CLImate lightGray(string $str = null)
 * @method CLImate darkGray(string $str = null)
 * @method CLImate lightRed(string $str = null)
 * @method CLImate lightGreen(string $str = null)
 * @method CLImate lightYellow(string $str = null)
 * @method CLImate lightBlue(string $str = null)
 * @method CLImate lightMagenta(string $str = null)
 * @method CLImate lightCyan(string $str = null)
 * @method CLImate white(string $str = null)
 *
 * @method CLImate backgroundBlack(string $str = null)
 * @method CLImate backgroundRed(string $str = null)
 * @method CLImate backgroundGreen(string $str = null)
 * @method CLImate backgroundYellow(string $str = null)
 * @method CLImate backgroundBlue(string $str = null)
 * @method CLImate backgroundMagenta(string $str = null)
 * @method CLImate backgroundCyan(string $str = null)
 * @method CLImate backgroundLightGray(string $str = null)
 * @method CLImate backgroundDarkGray(string $str = null)
 * @method CLImate backgroundLightRed(string $str = null)
 * @method CLImate backgroundLightGreen(string $str = null)
 * @method CLImate backgroundLightYellow(string $str = null)
 * @method CLImate backgroundLightBlue(string $str = null)
 * @method CLImate backgroundLightMagenta(string $str = null)
 * @method CLImate backgroundLightCyan(string $str = null)
 * @method CLImate backgroundWhite(string $str = null)
 *
 * @method CLImate bold(string $str = null)
 * @method CLImate dim(string $str = null)
 * @method CLImate underline(string $str = null)
 * @method CLImate blink(string $str = null)
 * @method CLImate invert(string $str = null)
 * @method CLImate hidden(string $str = null)
 *
 * @method CLImate info(string $str = null)
 * @method CLImate comment(string $str = null)
 * @method CLImate whisper(string $str = null)
 * @method CLImate shout(string $str = null)
 * @method CLImate error(string $str = null)
 *
 * @method mixed out(string $str)
 * @method mixed inline(string $str)
 * @method mixed table(array $data)
 * @method mixed json(mixed $var)
 * @method mixed br($count = 1)
 * @method mixed tab($count = 1)
 * @method mixed draw(string $art)
 * @method mixed border(string $char = null, integer $length = null)
 * @method mixed dump(mixed $var)
 * @method mixed flank(string $output, string $char = null, integer $length = null)
 * @method mixed progress(integer $total = null)
 * @method Spinner spinner(string $label = null, string ...$characters = null)
 * @method mixed padding(integer $length = 0, string $char = '.')
 * @method mixed input(string $prompt, Util\Reader\ReaderInterface $reader = null)
 * @method mixed confirm(string $prompt, Util\Reader\ReaderInterface $reader = null)
 * @method mixed password(string $prompt, Util\Reader\ReaderInterface $reader = null)
 * @method mixed checkboxes(string $prompt, array $options, Util\Reader\ReaderInterface $reader = null)
 * @method mixed radio(string $prompt, array $options, Util\Reader\ReaderInterface $reader = null)
 * @method mixed animation(string $art, TerminalObject\Helper\Sleeper $sleeper = null)
 * @method mixed columns(array $data, $column_count = null)
 * @method mixed clear()
 * @method CLImate clearLine()
 *
 * @method CLImate addArt(string $dir)
 */
class CLImate
{
    /**
     * An instance of the Style class
     *
     * @var \League\CLImate\Decorator\Style $style
     */
    public $style;

    /**
     * An instance of the Terminal Object Router class
     *
     * @var \League\CLImate\TerminalObject\Router\Router $router
     */
    protected $router;

    /**
     * An instance of the Settings Manager class
     *
     * @var \League\CLImate\Settings\Manager $settings
     */
    protected $settings;

    /**
     * An instance of the Argument Manager class
     *
     * @var \League\CLImate\Argument\Manager $arguments
     */
    public $arguments;

    /**
     * An instance of the Output class
     *
     * @var \League\CLImate\Util\Output $output
     */
    public $output;

    /**
     * An instance of the Util Factory
     *
     * @var \League\CLImate\Util\UtilFactory $util
     */
    protected $util;

    public function __construct()
    {
        $this->setStyle(new Style());
        $this->setRouter(new Router());
        $this->setSettingsManager(new SettingsManager());
        $this->setOutput(new Output());
        $this->setUtil(new UtilFactory());
        $this->setArgumentManager(new ArgumentManager());
    }

    /**
     * Set the style property
     *
     * @param \League\CLImate\Decorator\Style $style
     */
    public function setStyle(Style $style)
    {
        $this->style = $style;
    }

    /**
     * Set the router property
     *
     * @param \League\CLImate\TerminalObject\Router\Router $router
     */
    public function setRouter(Router $router)
    {
        $this->router = $router;
    }

    /**
     * Set the settings property
     *
     * @param \League\CLImate\Settings\Manager $manager
     */
    public function setSettingsManager(SettingsManager $manager)
    {
        $this->settings = $manager;
    }

    /**
     * Set the arguments property
     *
     * @param \League\CLImate\Argument\Manager $manager
     */
    public function setArgumentManager(ArgumentManager $manager)
    {
        $this->arguments = $manager;
    }

    /**
     * Set the output property
     *
     * @param \League\CLImate\Util\Output $output
     */
    public function setOutput(Output $output)
    {
        $this->output = $output;
    }

    /**
     * Set the util property
     *
     * @param \League\CLImate\Util\UtilFactory $util
     */
    public function setUtil(UtilFactory $util)
    {
        $this->util = $util;
    }

    /**
     * Extend CLImate with custom methods
     *
     * @param string|object|array $class
     * @param string $key Optional custom key instead of class name
     *
     * @return \League\CLImate\CLImate
     */
    public function extend($class, $key = null)
    {
        $this->router->addExtension($key, $class);

        return $this;
    }

    /**
     * Force ansi support on
     *
     * @return \League\CLImate\CLImate
     */
    public function forceAnsiOn()
    {
        $this->util->system->forceAnsi();

        return $this;
    }

    /**
     * Force ansi support off
     *
     * @return \League\CLImate\CLImate
     */
    public function forceAnsiOff()
    {
        $this->util->system->forceAnsi(false);

        return $this;
    }

    /**
     * Write line to writer once
     *
     * @param string|array $writer
     *
     * @return \League\CLImate\CLImate
     */
    public function to($writer)
    {
        $this->output->once($writer);

        return $this;
    }

    /**
     * Output the program's usage statement
     *
     * @param array $argv
     */
    public function usage(array $argv = null)
    {
        return $this->arguments->usage($this, $argv);
    }

    /**
     * Set the program's description
     *
     * @param string $description
     *
     * @return \League\CLImate\CLImate
     */
    public function description($description)
    {
        $this->arguments->description($description);

        return $this;
    }

    /**
     * Check if we have valid output
     *
     * @param  mixed   $output
     *
     * @return boolean
     */
    protected function hasOutput($output)
    {
        if (!empty($output)) {
            return true;
        }

        // Check for type first to avoid errors with objects/arrays/etc
        return ((is_string($output) || is_numeric($output)) && strlen($output) > 0);
    }

    /**
     * Search for the method within the string
     * and route it if we find one.
     *
     * @param  string $method
     * @param  string $name
     *
     * @return string The new string without the executed method.
     */
    protected function parseStyleMethod($method, $name)
    {
        // If the name starts with this method string...
        if (substr($name, 0, strlen($method)) == $method) {
            // ...remove the method name from the beginning of the string...
            $name = substr($name, strlen($method));

            // ...and trim off any of those underscores hanging around
            $name = ltrim($name, '_');

            $this->style->set($method);
        }

        return $name;
    }

    /**
     * Search for any style methods within the name and apply them
     *
     * @param  string $name
     * @param  array $method_search
     *
     * @return string Anything left over after applying styles
     */
    protected function applyStyleMethods($name, $method_search = null)
    {
        // Get all of the possible style attributes
        $method_search = $method_search ?: array_keys($this->style->all());

        $new_name = $this->searchForStyleMethods($name, $method_search);

        // While we still have a name left and we keep finding methods,
        // loop through the possibilities
        if (strlen($new_name) > 0 && $new_name != $name) {
            return $this->applyStyleMethods($new_name, $method_search);
        }

        return $new_name;
    }

    /**
     * Search for style methods in the current name
     *
     * @param string $name
     * @param array $search
     * @return string
     */
    protected function searchForStyleMethods($name, $search)
    {
        // Loop through the possible methods
        foreach ($search as $method) {
            // See if we found a valid method
            $name = $this->parseStyleMethod($method, $name);
        }

        return $name;
    }

    /**
     * Build up the terminal object and return it
     *
     * @param string $name
     * @param array $arguments
     *
     * @return object|null
     */
    protected function buildTerminalObject($name, $arguments)
    {
        // Retrieve the parser for the current set of styles
        $parser = $this->style->parser($this->util->system);

        // Reset the styles
        $this->style->reset();

        // Execute the terminal object
        $this->router->settings($this->settings);
        $this->router->parser($parser);
        $this->router->output($this->output);
        $this->router->util($this->util);

        return $this->router->execute($name, $arguments);
    }

    /**
     * Route anything leftover after styles were applied
     *
     * @param string $name
     * @param array $arguments
     *
     * @return object|null
     */
    protected function routeRemainingMethod($name, array $arguments)
    {
        // If we still have something left, let's figure out what it is
        if ($this->router->exists($name)) {
            $obj = $this->buildTerminalObject($name, $arguments);

            // If something was returned, return it
            if (is_object($obj)) {
                return $obj;
            }
        } elseif ($this->settings->exists($name)) {
            $this->settings->add($name, reset($arguments));
        // Handle passthroughs to the arguments manager.
        } else {
            // If we can't find it at this point, let's fail gracefully
            $this->out(reset($arguments));
        }
    }

    /**
     * Magic method for anything called that doesn't exist
     *
     * @param string $requested_method
     * @param array  $arguments
     *
     * @return \League\CLImate\CLImate|\League\CLImate\TerminalObject\Dynamic\DynamicTerminalObject
     *
     * List of many of the possible method being called here
     * documented at the top of this class.
     */
    public function __call($requested_method, $arguments)
    {
        // Apply any style methods that we can find first
        $name = $this->applyStyleMethods(Helper::snakeCase($requested_method));

        // The first argument is the string|array|object we want to echo out
        $output = reset($arguments);

        if (strlen($name)) {
            // If we have something left, let's try and route it to the appropriate place
            if ($result = $this->routeRemainingMethod($name, $arguments)) {
                return $result;
            }
        } elseif ($this->hasOutput($output)) {
            // If we have fulfilled all of the requested methods and we have output, output it
            $this->out($output);
        }

        return $this;
    }
}
climate/src/Settings/Art.php000064400000000671147361032630012021 0ustar00<?php

namespace League\CLImate\Settings;

class Art implements SettingsInterface
{
    /**
     * An array of valid art directories
     *  @var array[] $dirs
     */
    public $dirs = [];

    /**
     * Add directories of art
     */
    public function add()
    {
        $this->dirs = array_merge($this->dirs, func_get_args());
        $this->dirs = array_filter($this->dirs);
        $this->dirs = array_values($this->dirs);
    }
}
climate/src/Settings/Manager.php000064400000003321147361032630012640 0ustar00<?php

namespace League\CLImate\Settings;

class Manager
{
    /**
     * An array of settings that have been... set
     *
     * @var array $settings
     */
    protected $settings = [];

    /**
     * Check and see if the requested setting is a valid, registered setting
     *
     * @param  string  $name
     *
     * @return boolean
     */
    public function exists($name)
    {
        return class_exists($this->getPath($name));
    }

    /**
     * Add a setting
     *
     * @param string $name
     * @param mixed  $value
     */
    public function add($name, $value)
    {
        $setting = $this->getPath($name);
        $key     = $this->getClassName($name);

        // If the current key doesn't exist in the settings array, set it up
        if (!array_key_exists($name, $this->settings)) {
            $this->settings[$key] = new $setting();
        }

        $this->settings[$key]->add($value);
    }

    /**
     * Get the value of the requested setting if it exists
     *
     * @param  string $key
     *
     * @return mixed
     */
    public function get($key)
    {
        if (array_key_exists($key, $this->settings)) {
            return $this->settings[$key];
        }

        return false;
    }

    /**
     * Get the short name for the requested settings class
     *
     * @param  string $name
     *
     * @return string
     */
    protected function getPath($name)
    {
        return 'League\CLImate\Settings\\' . $this->getClassName($name);
    }

    /**
     * Get the short class name for the setting
     *
     * @param  string $name
     *
     * @return string
     */
    protected function getClassName($name)
    {
        return ucwords(str_replace('add_', '', $name));
    }
}
climate/src/Settings/SettingsImporter.php000064400000001205147361032630014607 0ustar00<?php

namespace League\CLImate\Settings;

trait SettingsImporter
{
    /**
     * Dictates any settings that a class may need access to
     *
     * @return array
     */
    public function settings()
    {
        return [];
    }

    /**
     * Import the setting into the class
     *
     * @param \League\CLImate\Settings\SettingsInterface $setting
     */
    public function importSetting($setting)
    {
        $short_name = basename(str_replace('\\', '/', get_class($setting)));

        $method = 'importSetting' . $short_name;

        if (method_exists($this, $method)) {
            $this->$method($setting);
        }
    }
}
climate/src/Settings/SettingsInterface.php000064400000000212147361032630014703 0ustar00<?php

namespace League\CLImate\Settings;

interface SettingsInterface
{
    /**
     * @return void
     */
    public function add();
}
climate/src/Exceptions/Exception.php000064400000000105147361032630013542 0ustar00<?php

namespace League\CLImate\Exceptions;

interface Exception
{
}
climate/src/Exceptions/UnexpectedValueException.php000064400000000207147361032630016567 0ustar00<?php

namespace League\CLImate\Exceptions;

class UnexpectedValueException extends \UnexpectedValueException implements Exception
{
}
climate/src/Exceptions/InvalidArgumentException.php000064400000000207147361032630016557 0ustar00<?php

namespace League\CLImate\Exceptions;

class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
}
climate/src/Exceptions/RuntimeException.php000064400000000167147361032630015116 0ustar00<?php

namespace League\CLImate\Exceptions;

class RuntimeException extends \RuntimeException implements Exception
{
}
climate/src/TerminalObject/Dynamic/Radio.php000064400000002077147361032630015021 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

class Radio extends Checkboxes
{
    /**
     * Build out the checkboxes
     *
     * @param array $options
     *
     * @return Checkbox\RadioGroup
     */
    protected function buildCheckboxes(array $options)
    {
        return new Checkbox\RadioGroup($options);
    }

    /**
     * Take the appropriate action based on the input character,
     * returns whether to stop listening or not
     *
     * @param string $char
     *
     * @return bool
     */
    protected function handleCharacter($char)
    {
        # Ignore space, as we can't select multiple options
        if ($char === " ") {
            return false;
        }

        # Use enter to select the current option
        if ($char === "\n") {
            $this->checkboxes->toggleCurrent();
        }

        return parent::handleCharacter($char);
    }

    /**
     * Format the prompt string
     *
     * @return string
     */
    protected function promptFormatted()
    {
        return $this->prompt . ' (press <Enter> to select)';
    }
}
climate/src/TerminalObject/Dynamic/Confirm.php000064400000001534147361032630015355 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Util\Reader\ReaderInterface;
use function in_array;
use function strtolower;
use function substr;

class Confirm extends Input
{


    /**
     * @inheritdoc
     */
    public function __construct($prompt, ReaderInterface $reader = null)
    {
        parent::__construct($prompt, $reader);

        $this->default = "n";
    }


    /**
     * Let us know if the user confirmed.
     *
     * @return bool
     */
    public function confirmed()
    {
        if (in_array($this->default, ["y", "yes"], true)) {
            $this->prompt .= " [Y/n]";
        } else {
            $this->prompt .= " [y/N]";
        }

        $this->accept(['y', 'yes', 'n', 'no'], false);

        $response = strtolower($this->prompt());

        return (substr($response, 0, 1) === 'y');
    }
}
climate/src/TerminalObject/Dynamic/Checkbox/Checkbox.php000064400000007753147361032630017245 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic\Checkbox;

use League\CLImate\Decorator\Parser\ParserImporter;
use League\CLImate\TerminalObject\Helper\StringLength;
use League\CLImate\Util\UtilImporter;

class Checkbox
{
    use StringLength, ParserImporter, UtilImporter;

    /**
     * The value of the checkbox
     *
     * @var string|int|bool $value
     */
    protected $value;

    /**
     * The label for the checkbox
     *
     * @var string|int $label
     */
    protected $label;

    /**
     * Whether the checkbox is checked
     *
     * @var bool $checked
     */
    protected $checked = false;

    /**
     * Whether pointer is currently pointing at the checkbox
     *
     * @var bool $current
     */
    protected $current = false;

    /**
     * Whether the checkbox is the first in the group
     *
     * @var bool $first
     */
    protected $first = false;

    /**
     * Whether the checkbox is the last in the group
     *
     * @var bool $last
     */
    protected $last = false;

    public function __construct($label, $value)
    {
        $this->value = (!is_int($value)) ? $value : $label;
        $this->label = $label;
    }

    /**
     * @return bool
     */
    public function isCurrent()
    {
        return $this->current;
    }

    /**
     * @return bool
     */
    public function isChecked()
    {
        return $this->checked;
    }

    /**
     * @return bool
     */
    public function isFirst()
    {
        return $this->first;
    }

    /**
     * @return bool
     */
    public function isLast()
    {
        return $this->last;
    }

    /**
     * Set whether the pointer is currently pointing at this checkbox
     *
     * @param bool $current
     *
     * @return Checkbox
     */
    public function setCurrent($current = true)
    {
        $this->current = $current;

        return $this;
    }

    /**
     * Set whether the checkbox is currently checked
     *
     * @param bool $checked
     *
     * @return Checkbox
     */
    public function setChecked($checked = true)
    {
        $this->checked = $checked;

        return $this;
    }

    /**
     * @return Checkbox
     */
    public function setFirst()
    {
        $this->first = true;

        return $this;
    }

    /**
     * @return Checkbox
     */
    public function setLast()
    {
        $this->last = true;

        return $this;
    }

    /**
     * @return string|int|bool
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Build out basic checkbox string based on current options
     *
     * @return string
     */
    protected function buildCheckboxString()
    {
        $parts = [
            ($this->isCurrent()) ? $this->pointer() : ' ',
            $this->checkbox($this->isChecked()),
            $this->label,
        ];

        $line = implode(' ', $parts);

        return $line . $this->getPaddingString($line);
    }

    /**
     * Get the padding string based on the length of the terminal/line
     *
     * @param string $line
     *
     * @return string
     */
    protected function getPaddingString($line)
    {
        $length = $this->util->width() - $this->lengthWithoutTags($line);

        return str_repeat(' ', $length);
    }

    /**
     * Get the checkbox symbol
     *
     * @param bool $checked
     *
     * @return string
     */
    protected function checkbox($checked)
    {
        if ($checked) {
            return html_entity_decode("&#x25CF;");
        }

        return html_entity_decode("&#x25CB;");
    }

    /**
     * Get the pointer symbol
     *
     * @return string
     */
    protected function pointer()
    {
        return html_entity_decode("&#x276F;");
    }

    public function __toString()
    {
        if ($this->isFirst()) {
            return $this->buildCheckboxString();
        }

        if ($this->isLast()) {
            return $this->buildCheckboxString() . $this->util->cursor->left(10) . '<hidden>';
        }

        return $this->buildCheckboxString();
    }
}
climate/src/TerminalObject/Dynamic/Checkbox/RadioGroup.php000064400000001463147361032630017562 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic\Checkbox;

class RadioGroup extends CheckboxGroup
{
    /**
     * Toggle the currently selected option, uncheck all of the others
     */
    public function toggleCurrent()
    {
        list($checkbox, $checkbox_key) = $this->getCurrent();

        $checkbox->setChecked(!$checkbox->isChecked());

        foreach ($this->checkboxes as $key => $checkbox) {
            if ($key == $checkbox_key) {
                continue;
            }

            $checkbox->setChecked(false);
        }
    }

    /**
     * Get the checked option
     *
     * @return string|bool|int
     */
    public function getCheckedValues()
    {
        if ($checked = $this->getChecked()) {
            return reset($checked)->getValue();
        }

        return null;
    }
}
climate/src/TerminalObject/Dynamic/Checkbox/CheckboxGroup.php000064400000007636147361032630020262 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic\Checkbox;

use League\CLImate\Decorator\Parser\ParserImporter;
use League\CLImate\Util\OutputImporter;
use League\CLImate\Util\UtilImporter;

class CheckboxGroup
{
    use OutputImporter, ParserImporter, UtilImporter;

    protected $checkboxes = [];

    protected $count;

    public function __construct(array $options)
    {
        foreach ($options as $key => $option) {
            $this->checkboxes[] = new Checkbox($option, $key);
        }

        $this->count = count($this->checkboxes);

        $this->checkboxes[0]->setFirst()->setCurrent();
        $this->checkboxes[$this->count - 1]->setLast();
    }

    public function write()
    {
        array_map([$this, 'writeCheckbox'], $this->checkboxes);
    }

    /**
     * Retrieve the checked option values
     *
     * @return array
     */
    public function getCheckedValues()
    {
        return array_values(array_map([$this, 'getValue'], $this->getChecked()));
    }

    /**
     * Set the newly selected option based on the direction
     *
     * @param string $direction 'previous' or 'next'
     */
    public function setCurrent($direction)
    {
        list($option, $key) = $this->getCurrent();

        $option->setCurrent(false);

        $new_key = $this->getCurrentKey($direction, $option, $key);

        $this->checkboxes[$new_key]->setCurrent();
    }

    /**
     * Toggle the current option's checked status
     */
    public function toggleCurrent()
    {
        list($option) = $this->getCurrent();

        $option->setChecked(!$option->isChecked());
    }

    /**
     * Get the number of checkboxes
     *
     * @return int
     */
    public function count()
    {
        return $this->count;
    }

    /**
     * Retrieve the checked options
     *
     * @return array
     */
    protected function getChecked()
    {
        return array_filter($this->checkboxes, [$this, 'isChecked']);
    }

    /**
     * Determine whether the option is checked
     *
     * @param Checkbox $option
     *
     * @return bool
     */
    protected function isChecked($option)
    {
        return $option->isChecked();
    }

    /**
     * Retrieve the option's value
     *
     * @param Checkbox $option
     *
     * @return mixed
     */
    protected function getValue($option)
    {
        return $option->getValue();
    }

    /**
     * Get the currently selected option
     *
     * @return array
     */
    protected function getCurrent()
    {
        foreach ($this->checkboxes as $key => $option) {
            if ($option->isCurrent()) {
                return [$option, $key];
            }
        }
    }

    /**
     * Retrieve the correct current key
     *
     * @param string $direction 'previous' or 'next'
     * @param Checkbox $option
     * @param int $key
     *
     * @return int
     */
    protected function getCurrentKey($direction, $option, $key)
    {
        $method = 'get' . ucwords($direction). 'Key';

        return $this->{$method}($option, $key);
    }

    /**
     * @param Checkbox $option
     * @param int $key
     *
     * @return int
     */
    protected function getPreviousKey($option, $key)
    {
        if ($option->isFirst()) {
            return count($this->checkboxes) - 1;
        }

        return --$key;
    }

    /**
     * @param Checkbox $option
     * @param int $key
     *
     * @return int
     */
    protected function getNextKey($option, $key)
    {
        if ($option->isLast()) {
            return 0;
        }

        return ++$key;
    }

    /**
     * @param Checkbox $checkbox
     */
    protected function writeCheckbox($checkbox)
    {
        $checkbox->util($this->util);
        $checkbox->parser($this->parser);

        $parsed = $this->parser->apply((string) $checkbox);

        if ($checkbox->isLast()) {
            $this->output->sameLine()->write($parsed);
            return;
        }

        $this->output->write($parsed);
    }
}
climate/src/TerminalObject/Dynamic/Checkboxes.php000064400000007243147361032630016041 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Util\Reader\ReaderInterface;
use League\CLImate\Util\Reader\Stdin;

class Checkboxes extends InputAbstract
{
    /**
     * The options to choose from
     *
     * @var Checkbox\CheckboxGroup $checkboxes
     */
    protected $checkboxes;

    public function __construct($prompt, array $options, ReaderInterface $reader = null)
    {
        $this->prompt  = $prompt;
        $this->reader  = $reader ?: new Stdin();

        $this->checkboxes = $this->buildCheckboxes($options);
    }

    /**
     * Do it! Prompt the user for information!
     *
     * @return string
     */
    public function prompt()
    {
        $this->output->write($this->parser->apply($this->promptFormatted()));

        $this->writeCheckboxes();

        $this->util->system->exec('stty sane');

        return $this->checkboxes->getCheckedValues();
    }

    /**
     * Build out the checkboxes
     *
     * @param array $options
     *
     * @return Checkbox\CheckboxGroup
     */
    protected function buildCheckboxes(array $options)
    {
        return new Checkbox\CheckboxGroup($options);
    }

    /**
     * Format the prompt string
     *
     * @return string
     */
    protected function promptFormatted()
    {
        return $this->prompt . ' (use <space> to select)';
    }

    /**
     * Output the checkboxes and listen for any keystrokes
     */
    protected function writeCheckboxes()
    {
        $this->updateCheckboxView();

        $this->util->system->exec('stty -icanon');
        $this->output->sameLine()->write($this->util->cursor->hide());

        $this->listenForInput();
    }

    /**
     * Listen for input and act on it
     */
    protected function listenForInput()
    {
        while ($char = $this->reader->char(1)) {
            if ($this->handleCharacter($char)) {
                break;
            }

            $this->moveCursorToTop();
            $this->updateCheckboxView();
        }
    }

    /**
     * Take the appropriate action based on the input character,
     * returns whether to stop listening or not
     *
     * @param string $char
     *
     * @return bool
     */
    protected function handleCharacter($char)
    {
        switch ($char) {
            case "\n":
                $this->output->sameLine()->write($this->util->cursor->defaultStyle());
                $this->output->sameLine()->write("\e[0m");
            return true; // Break the while loop as well

            case "\e":
                $this->handleAnsi();
            break;

            case ' ':
                $this->checkboxes->toggleCurrent();
            break;
        }

        return false;
    }

    /**
     * Move the cursor to the top of the option list
     */
    protected function moveCursorToTop()
    {
        $output = $this->util->cursor->up($this->checkboxes->count() - 1);
        $output .= $this->util->cursor->startOfCurrentLine();

        $this->output->sameLine()->write($output);
    }

    /**
     * Handle any ANSI characters
     */
    protected function handleAnsi()
    {
        switch ($this->reader->char(2)) {
            // Up arrow
            case '[A':
                $this->checkboxes->setCurrent('previous');
            break;

            // Down arrow
            case '[B':
                $this->checkboxes->setCurrent('next');
            break;
        }
    }

    /**
     * Re-write the checkboxes based on the current objects
     */
    protected function updateCheckboxView()
    {
        $this->checkboxes->util($this->util);
        $this->checkboxes->output($this->output);
        $this->checkboxes->parser($this->parser);

        $this->checkboxes->write();
    }
}
climate/src/TerminalObject/Dynamic/Password.php000064400000000312147361032630015553 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

class Password extends Input
{
    public function prompt()
    {
        $this->writePrompt();

        return $this->reader->hidden();
    }
}
climate/src/TerminalObject/Dynamic/InputAbstract.php000064400000001363147361032630016543 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Util\Reader\ReaderInterface;
use League\CLImate\Util\Reader\Stdin;

abstract class InputAbstract extends DynamicTerminalObject
{
    /**
     * The prompt text
     *
     * @var string $prompt
     */
    protected $prompt;

    /**
     * An instance of ReaderInterface
     *
     * @var \League\CLImate\Util\Reader\ReaderInterface $reader
     */
    protected $reader;

    /**
     * Do it! Prompt the user for information!
     *
     * @return string
     */
    abstract public function prompt();

    /**
     * Format the prompt incorporating spacing and any acceptable options
     *
     * @return string
     */
    abstract protected function promptFormatted();
}
climate/src/TerminalObject/Dynamic/Animation/Keyframe.php000064400000015073147361032630017445 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic\Animation;

use League\CLImate\Decorator\Parser\ParserImporter;
use League\CLImate\TerminalObject\Helper\StringLength;
use League\CLImate\Util\UtilImporter;

class Keyframe
{
    use StringLength, ParserImporter, UtilImporter;

    /**
     * Get the enter keyframes for the desired direction
     *
     * @param array $lines
     * @param string $direction
     *
     * @return array
     */
    public function enterFrom($lines, $direction)
    {
        return array_reverse($this->exitTo($lines, $direction));
    }

    /**
     * Get the exit keyframes for the desired direction
     *
     * @param array $lines
     * @param string $direction
     *
     * @return array
     */
    public function exitTo($lines, $direction)
    {
        $lines       = $this->adjustLines($lines, $direction);
        $line_method = $this->getLineMethod($direction);

        $direction_keyframes = $this->getDirectionFrames($direction, $lines, $line_method);

        $keyframes   = array_fill(0, 4, $lines);
        $keyframes   = array_merge($keyframes, $direction_keyframes);
        $keyframes[] = array_fill(0, count($lines), '');

        return $keyframes;
    }

    /**
     * Get scroll keyframes
     *
     * @param array $lines
     * @param string $enter_from
     * @param string $exit_to
     *
     * @return array
     */
    public function scroll($lines, $enter_from, $exit_to)
    {
        $keyframes   = $this->enterFrom($lines, $enter_from);
        $keyframes   = array_merge($keyframes, $this->exitTo($lines, $exit_to));
        $keyframes   = array_unique($keyframes, SORT_REGULAR);
        $keyframes[] = reset($keyframes);

        return $keyframes;
    }

    /**
     * Get the line parser for the direction
     *
     * @param string $direction
     * @return string
     */
    protected function getLineMethod($direction)
    {
        return 'current' . ucwords(strtolower($direction)) . 'Line';
    }

    /**
     * Adjust the array of lines if necessary
     *
     * @param array $lines
     * @param string $direction
     *
     * @return array
     */
    protected function adjustLines(array $lines, $direction)
    {
        $adjust_method = 'adjust' . ucwords(strtolower($direction))  . 'Lines';

        if (method_exists($this, $adjust_method)) {
            return $this->$adjust_method($lines);
        }

        return $lines;
    }

    /**
     * Pad the array of lines for "right" animation
     *
     * @param array $lines
     * @return array
     */
    protected function adjustRightLines(array $lines)
    {
        return $this->padArray($lines, $this->util->width());
    }

    /**
     * Pad the array of lines for "left" animation
     *
     * @param array $lines
     * @return array
     */
    protected function adjustLeftLines(array $lines)
    {
        return $this->padArray($lines, $this->maxStrLen($lines));
    }

    /**
     * Get the keyframes appropriate for the animation direction
     *
     * @param string $direction
     * @param array $lines
     * @param string $line_method
     *
     * @return array
     */
    protected function getDirectionFrames($direction, array $lines, $line_method)
    {
        $mapping = [
            'exitHorizontalFrames' => ['left', 'right'],
            'exitVerticalFrames'   => ['top', 'bottom'],
        ];

        foreach ($mapping as $method => $directions) {
            if (in_array($direction, $directions)) {
                return $this->$method($lines, $line_method);
            }
        }

        // Fail gracefully, simply return an array
        return [];
    }

    /**
     * Create horizontal exit animation keyframes for the art
     *
     * @param array $lines
     * @param string $line_method
     *
     * @return array
     */
    protected function exitHorizontalFrames(array $lines, $line_method)
    {
        $keyframes = [];
        $length    = mb_strlen($lines[0]);

        for ($i = $length; $i > 0; $i--) {
            $keyframes[] = $this->getHorizontalKeyframe($lines, $i, $line_method, $length);
        }

        return $keyframes;
    }

    /**
     * Get the keyframe for a horizontal animation
     *
     * @param array $lines
     * @param int $frame_number
     * @param string $line_method
     * @param int $length
     *
     * @return array
     */
    protected function getHorizontalKeyframe(array $lines, $frame_number, $line_method, $length)
    {
        $keyframe = [];

        foreach ($lines as $line) {
            $keyframe[] = $this->$line_method($line, $frame_number, $length);
        }

        return $keyframe;
    }

    /**
     * Create vertical exit animation keyframes for the art
     *
     * @param array $lines
     * @param string $line_method
     *
     * @return array
     */
    protected function exitVerticalFrames(array $lines, $line_method)
    {
        $keyframes  = [];
        $line_count = count($lines);

        for ($i = $line_count - 1; $i >= 0; $i--) {
            $keyframes[] = $this->$line_method($lines, $line_count, $i);
        }

        return $keyframes;
    }

    /**
     * Get the current line as it is exiting left
     *
     * @param string $line
     * @param int $frame_number
     *
     * @return string
     */
    protected function currentLeftLine($line, $frame_number)
    {
        return mb_substr($line, -$frame_number);
    }


    /**
     * Get the current line as it is exiting right
     *
     * @param string $line
     * @param int $frame_number
     * @param int $length
     *
     * @return string
     */
    protected function currentRightLine($line, $frame_number, $length)
    {
        return str_repeat(' ', $length - $frame_number) . mb_substr($line, 0, $frame_number);
    }

    /**
     * Slice off X number of lines from the bottom and fill the rest with empty strings
     *
     * @param array $lines
     * @param integer $total_lines
     * @param integer $current
     *
     * @return array
     */
    protected function currentTopLine($lines, $total_lines, $current)
    {
        $keyframe = array_slice($lines, -$current, $current);

        return array_merge($keyframe, array_fill(0, $total_lines - $current, ''));
    }

    /**
     * Slice off X number of lines from the top and fill the rest with empty strings
     *
     * @param array $lines
     * @param integer $total_lines
     * @param integer $current
     *
     * @return array
     */
    protected function currentBottomLine($lines, $total_lines, $current)
    {
        $keyframe = array_fill(0, $total_lines - $current, '');

        return array_merge($keyframe, array_slice($lines, 0, $current));
    }
}
climate/src/TerminalObject/Dynamic/Animation.php000064400000012341147361032630015675 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\TerminalObject\Dynamic\Animation\Keyframe;
use League\CLImate\TerminalObject\Helper\Art;
use League\CLImate\TerminalObject\Helper\Sleeper;

class Animation extends DynamicTerminalObject
{
    use Art;

    /**
     * @var \League\CLImate\TerminalObject\Helper\Sleeper $sleeper
     */
    protected $sleeper;

    /**
     * @var \League\CLImate\TerminalObject\Dynamic\Animation\Keyframe $keyframes
     */
    protected $keyframes;

    public function __construct($art, Sleeper $sleeper = null, Keyframe $keyframes = null)
    {
        // Add the default art directory
        $this->addDir(__DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . 'ASCII');

        $this->setSleeper($sleeper);
        $this->setKeyFrames($keyframes);

        $this->art = $art;
    }

    /**
     * Run a basic animation
     */
    public function run()
    {
        $files     = $this->artDir($this->art);
        $animation = [];

        foreach ($files as $file) {
            $animation[] = $this->parse($file);
        }

        $this->animate($animation);
    }

    /**
     * Set the speed of the animation based on a percentage
     * (50% slower, 200% faster, etc)
     *
     * @param int|float $percentage
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Animation
     */
    public function speed($percentage)
    {
        $this->sleeper->speed($percentage);

        return $this;
    }

    /**
     * Scroll the art
     *
     * @param string $direction
     * @return bool
     */
    public function scroll($direction = 'right')
    {
        $this->setupKeyframes();

        $mapping = $this->getScrollDirectionMapping();

        if (!array_key_exists($direction, $mapping)) {
            return false;
        }

        $lines       = $this->getLines();
        $enter_from  = $mapping[$direction];
        $exit_to     = $mapping[$enter_from];

        $this->animate($this->keyframes->scroll($lines, $enter_from, $exit_to));
    }

    /**
     * Animate the art exiting the screen
     *
     * @param string $direction top|bottom|right|left
     */
    public function exitTo($direction)
    {
        $this->setupKeyframes();

        $this->animate($this->keyframes->exitTo($this->getLines(), $direction));
    }

    /**
     * Animate the art entering the screen
     *
     * @param string $direction top|bottom|right|left
     */
    public function enterFrom($direction)
    {
        $this->setupKeyframes();

        $this->animate($this->keyframes->enterFrom($this->getLines(), $direction));
    }

    protected function getScrollDirectionMapping()
    {
        return [
            'left'   => 'right',
            'right'  => 'left',
            'top'    => 'bottom',
            'bottom' => 'top',
            'up'     => 'bottom',
            'down'   => 'top',
        ];
    }

    protected function getLines()
    {
        return $this->parse($this->artFile($this->art));
    }

    /**
     * @param \League\CLImate\TerminalObject\Helper\Sleeper $sleeper
     */
    protected function setSleeper($sleeper = null)
    {
        $this->sleeper = $sleeper ?: new Sleeper();
    }

    /**
     * @param League\CLImate\TerminalObject\Dynamic\Animation\Keyframe $keyframes
     */
    protected function setKeyFrames($keyframes)
    {
        $this->keyframes = $keyframes ?: new Keyframe;
    }

    /**
     * Set up the necessary properties on the Keyframe class
     */
    protected function setupKeyframes()
    {
        $this->keyframes->parser($this->parser);
        $this->keyframes->util($this->util);
    }

    /**
     * Animate the given keyframes
     *
     * @param array $keyframes Array of arrays
     */
    protected function animate(array $keyframes)
    {
        $count = 0;

        foreach ($keyframes as $lines) {
            $this->writeKeyFrame($lines, $count);
            $this->sleeper->sleep();
            $count = count($lines);
        }
    }

    /**
     * Write the current keyframe to the terminal, line by line
     *
     * @param array $lines
     * @param integer $count
     */
    protected function writeKeyFrame(array $lines, $count)
    {
        foreach ($lines as $key => $line) {
            $content = $this->getLineFormatted($line, $key, $count);
            $this->output->write($this->parser->apply($content));
        }
    }

    /**
     * Format the line to re-write previous lines, if necessary
     *
     * @param string $line
     * @param integer $key
     * @param integer $last_frame_count
     *
     * @return string
     */
    protected function getLineFormatted($line, $key, $last_frame_count)
    {
        // If this is the first thing we're writing, just return the line
        if ($last_frame_count == 0) {
            return $line;
        }

        $content = '';

        // If this is the first line of the frame,
        // move the cursor up the total number of previous lines from the previous frame
        if ($key == 0) {
            $content .= $this->util->cursor->up($last_frame_count);
        }

        $content .= $this->util->cursor->startOfCurrentLine();
        $content .= $this->util->cursor->deleteCurrentLine();
        $content .= $line;

        return $content;
    }
}
climate/src/TerminalObject/Dynamic/Input.php000064400000014162147361032630015060 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Util\Reader\ReaderInterface;
use League\CLImate\Util\Reader\Stdin;

class Input extends InputAbstract
{
    /**
     * An array of acceptable responses
     *
     * @var array|object $acceptable
     */
    protected $acceptable;

    /**
     * Whether we should be strict about the response given
     *
     * @var boolean $strict
     */
    protected $strict = false;

    /**
     * Whether to accept multiple lines of input
     *
     * Terminated by ^D
     *
     * @var boolean $multiLine
     */
    protected $multiLine = false;

    /**
     * Whether we should display the acceptable responses to the user
     *
     * @var boolean $show_acceptable
     */
    protected $show_acceptable = false;

    /**
     * A default answer in the case of no user response,
     * prevents re-prompting
     *
     * @var string
     */
    protected $default = '';

    public function __construct($prompt, ReaderInterface $reader = null)
    {
        $this->prompt = $prompt;
        $this->reader = $reader ?: new Stdin();
    }

    /**
     * Do it! Prompt the user for information!
     *
     * @return string
     */
    public function prompt()
    {
        $this->writePrompt();

        $response = $this->valueOrDefault($this->getUserInput());

        if ($this->isValidResponse($response)) {
            return $response;
        }

        return $this->prompt();
    }

    /**
     * Define the acceptable responses and whether or not to
     * display them to the user
     *
     * @param  array|object $acceptable
     * @param  boolean $show
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Input
     */
    public function accept($acceptable, $show = false)
    {
        $this->acceptable      = $acceptable;
        $this->show_acceptable = $show;

        return $this;
    }

    /**
     * Define whether we should be strict about exact responses
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Input
     */
    public function strict()
    {
        $this->strict = true;

        return $this;
    }

    /**
     * Set a default response
     *
     * @param string $default
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Input
     */
    public function defaultTo($default)
    {
        $this->default = $default;

        return $this;
    }

    /**
     * Set multiline input to true
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Input
     */
    public function multiLine()
    {
        $this->multiLine = true;

        return $this;
    }

    /**
     * @return string
     */
    protected function getUserInput()
    {
        if ($this->multiLine) {
            return $this->reader->multiLine();
        }

        return $this->reader->line();
    }

    /**
     * Write out the formatted prompt
     */
    protected function writePrompt()
    {
        $prompt = $this->parser->apply($this->promptFormatted());

        $this->output->sameLine()->write($prompt);
    }

    /**
     * If no response was given and there is a default, return it,
     * otherwise return response
     *
     * @param string $response
     *
     * @return string
     */
    protected function valueOrDefault($response)
    {
        if (strlen($response) == 0 && strlen($this->default)) {
            return $this->default;
        }

        return $response;
    }

    /**
     * Format the acceptable responses as options
     *
     * @return string
     */
    protected function acceptableFormatted()
    {
        if (!is_array($this->acceptable)) {
            return '';
        }

        $acceptable = array_map([$this, 'acceptableItemFormatted'], $this->acceptable);

        return '[' . implode('/', $acceptable) . ']';
    }

    /**
     * Format the acceptable item depending on whether it is the default or not
     *
     * @param string $item
     *
     * @return string
     */
    protected function acceptableItemFormatted($item)
    {
        if ($item == $this->default) {
            return '<bold>' . $item . '</bold>';
        }

        return $item;
    }

    /**
     * Format the prompt incorporating spacing and any acceptable options
     *
     * @return string
     */
    protected function promptFormatted()
    {
        $prompt = $this->prompt . ' ';

        if ($this->show_acceptable) {
            $prompt .= $this->acceptableFormatted() . ' ';
        }

        return $prompt;
    }

    /**
     * Apply some string manipulation functions for normalization
     *
     * @param string|array $var
     * @return array
     */
    protected function levelPlayingField($var)
    {
        $levelers = ['trim', 'mb_strtolower'];

        foreach ($levelers as $leveler) {
            if (is_array($var)) {
                $var = array_map($leveler, $var);
            } else {
                $var = $leveler($var);
            }
        }

        return $var;
    }

    /**
     * Determine whether or not the acceptable property is of type closure
     *
     * @return boolean
     */
    protected function acceptableIsClosure()
    {
        return (is_object($this->acceptable) && $this->acceptable instanceof \Closure);
    }

    /**
     * Determine if the user's response is in the acceptable responses array
     *
     * @param string $response
     *
     * @return boolean $response
     */
    protected function isAcceptableResponse($response)
    {
        if ($this->strict) {
            return in_array($response, $this->acceptable);
        }

        $acceptable = $this->levelPlayingField($this->acceptable);
        $response   = $this->levelPlayingField($response);

        return in_array($response, $acceptable);
    }

    /**
     * Determine if the user's response is valid based on the current settings
     *
     * @param string $response
     *
     * @return boolean $response
     */
    protected function isValidResponse($response)
    {
        if (empty($this->acceptable)) {
            return true;
        }

        if ($this->acceptableIsClosure()) {
            return call_user_func($this->acceptable, $response);
        }

        return $this->isAcceptableResponse($response);
    }
}
climate/src/TerminalObject/Dynamic/Spinner.php000064400000007155147361032630015403 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Exceptions\UnexpectedValueException;
use function array_merge;
use function count;
use function microtime;
use function range;
use function str_repeat;
use function substr;
use function trim;

final class Spinner extends DynamicTerminalObject
{
    /**
     * @var string[] The characters to be used to present progress.
     */
    private $characters = ["[=---]", "[-=--]", "[--=-]", "[---=]", "[--=-]", "[-=--]"];

    /**
     * @var int The current item of the sequence
     */
    private $current = 0;

    /**
     * @var bool Flag indicating whether we are writing the bar for the first time.
     */
    private $firstLine = true;

    /**
     * @var string Current label.
     */
    private $label;

    /**
     * @var float When the spinner was last drawn.
     */
    private $lastDrawn;

    /**
     * @var float How long to wait in seconds between drawing each stage.
     */
    private $timeLimit = 0.1;


    /**
     * If they pass in a sequence, set the sequence
     *
     * @param string $label
     * @param string ...$characters
     */
    public function __construct($label = null, ...$characters)
    {
        if ($label !== null) {
            $this->label = $label;
        }

        if (count($characters) < 1) {
            $characters = [];
            $size = 5;
            $positions = array_merge(range(0, $size - 1), range($size - 2, 1, -1));
            foreach ($positions as $pos) {
                $line = str_repeat("-", $size);
                $characters[] = "[" . substr($line, 0, $pos) . "=" . substr($line, $pos + 1) . "]";
            }
        }
        $this->characters(...$characters);
    }


    /**
     * Set the length of time to wait between drawing each stage.
     *
     * @param float $timeLimit
     *
     * @return Spinner
     */
    public function timeLimit($timeLimit)
    {
        $this->timeLimit = (float) $timeLimit;

        return $this;
    }


    /**
     * Set the character to loop around.
     *
     * @param string $characters
     *
     * @return Spinner
     */
    public function characters(...$characters)
    {
        if (count($characters) < 1) {
            throw new UnexpectedValueException("You must specify the characters to use");
        }

        $this->characters = $characters;

        return $this;
    }


    /**
     * Re-writes the spinner
     *
     * @param string $label
     *
     * @return void
     */
    public function advance($label = null)
    {
        if ($label === null) {
            $label = $this->label;
        }

        if ($this->lastDrawn) {
            $time = microtime(true) - $this->lastDrawn;
            if ($time < $this->timeLimit) {
                return;
            }
        }

        ++$this->current;
        if ($this->current >= count($this->characters)) {
            $this->current = 0;
        }

        $characters = $this->characters[$this->current];
        $this->drawSpinner($characters, $label);
        $this->lastDrawn = microtime(true);
    }


    /**
     * Draw the spinner
     *
     * @param string $characters
     * @param string $label
     */
    private function drawSpinner($characters, $label)
    {
        $spinner = "";

        if ($this->firstLine) {
            $this->firstLine = false;
        } else {
            $spinner .= $this->util->cursor->up(1);
            $spinner .= $this->util->cursor->startOfCurrentLine();
            $spinner .= $this->util->cursor->deleteCurrentLine();
        }

        $spinner .= trim("{$characters} {$label}");

        $this->output->write($this->parser->apply($spinner));
    }
}
climate/src/TerminalObject/Dynamic/Progress.php000064400000017267147361032630015576 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Exceptions\UnexpectedValueException;

class Progress extends DynamicTerminalObject
{
    /**
     * The total number of items involved
     *
     * @var integer $total
     */
    protected $total = 0;

    /**
     * The current item that the progress bar represents
     *
     * @var integer $current
     */
    protected $current = 0;

    /**
     * The current percentage displayed
     *
     * @var string $current_percentage
     */
    protected $current_percentage = '';

    /**
     * The string length of the bar when at 100%
     *
     * @var integer $bar_str_len
     */
    protected $bar_str_len;

    /**
     * Flag indicating whether we are writing the bar for the first time
     *
     * @var boolean $first_line
     */
    protected $first_line = true;

    /**
     * Current status bar label
     *
     * @var string $label
     */
    protected $label;

    /**
     * Force a redraw every time
     *
     * @var boolean $force_redraw
     */
    protected $force_redraw = false;

    /**
     * If this progress bar ever displayed a label.
     *
     * @var boolean $has_label_line
     */
    protected $has_label_line = false;

    /**
     * If they pass in a total, set the total
     *
     * @param integer $total
     */
    public function __construct($total = null)
    {
        if ($total !== null) {
            $this->total($total);
        }
    }

    /**
     * Set the total property
     *
     * @param  integer $total
     *
     * @return Progress
     */
    public function total($total)
    {
        $this->total = $total;

        return $this;
    }

    /**
     * Determines the current percentage we are at and re-writes the progress bar
     *
     * @param integer $current
     * @param mixed   $label
     *
     * @return void
     * @throws UnexpectedValueException
     */
    public function current($current, $label = null)
    {
        if ($this->total == 0) {
            // Avoid dividing by 0
            throw new UnexpectedValueException('The progress total must be greater than zero.');
        }

        if ($current > $this->total) {
            throw new UnexpectedValueException('The current is greater than the total.');
        }

        $this->drawProgressBar($current, $label);

        $this->current = $current;
        $this->label   = $label;
    }

    /**
     * Increments the current position we are at and re-writes the progress bar
     *
     * @param integer $increment The number of items to increment by
     * @param string $label
     */
    public function advance($increment = 1, $label = null)
    {
        $this->current($this->current + $increment, $label);
    }

    /**
     * Force the progress bar to redraw every time regardless of whether it has changed or not
     *
     * @param boolean $force
     * @return Progress
     */
    public function forceRedraw($force = true)
    {
        $this->force_redraw = !!$force;

        return $this;
    }


    /**
     * Update a progress bar using an iterable.
     *
     * @param iterable $items Array or any other iterable object
     * @param callable $callback A handler to run on each item
     */
    public function each($items, callable $callback = null)
    {
        if ($items instanceof \Traversable) {
            $items = iterator_to_array($items);
        }

        $total = count($items);
        if (!$total) {
            return;
        }
        $this->total($total);

        foreach ($items as $key => $item) {
            if ($callback) {
                $label = $callback($item, $key);
            } else {
                $label = null;
            }

            $this->advance(1, $label);
        }
    }


    /**
     * Draw the progress bar, if necessary
     *
     * @param string $current
     * @param string $label
     */
    protected function drawProgressBar($current, $label)
    {
        $percentage = $this->percentageFormatted($current / $this->total);

        if ($this->shouldRedraw($percentage, $label)) {
            $progress_bar = $this->getProgressBar($current, $label);
            $this->output->write($this->parser->apply($progress_bar));
        }

        $this->current_percentage = $percentage;
    }

    /**
     * Build the progress bar str and return it
     *
     * @param integer $current
     * @param string $label
     *
     * @return string
     */
    protected function getProgressBar($current, $label)
    {
        if ($this->first_line) {
            // Drop down a line, we are about to
            // re-write this line for the progress bar
            $this->output->write('');
            $this->first_line = false;
        }

        // Move the cursor up and clear it to the end
        $line_count = $this->has_label_line ? 2 : 1;

        $progress_bar  = $this->util->cursor->up($line_count);
        $progress_bar .= $this->util->cursor->startOfCurrentLine();
        $progress_bar .= $this->util->cursor->deleteCurrentLine();
        $progress_bar .= $this->getProgressBarStr($current, $label);

        // If this line has a label then set that this progress bar has a label line
        if (strlen($label) > 0) {
            $this->has_label_line = true;
        }

        return $progress_bar;
    }

    /**
     * Get the progress bar string, basically:
     * =============>             50% label
     *
     * @param integer $current
     * @param string $label
     *
     * @return string
     */
    protected function getProgressBarStr($current, $label)
    {
        $percentage = $current / $this->total;
        $bar_length = round($this->getBarStrLen() * $percentage);

        $bar        = $this->getBar($bar_length);
        $number     = $this->percentageFormatted($percentage);

        if ($label) {
            $label = $this->labelFormatted($label);
        // If this line doesn't have a label, but we've had one before,
        // then ensure the label line is cleared
        } elseif ($this->has_label_line) {
            $label = $this->labelFormatted('');
        }

        return trim("{$bar} {$number}{$label}");
    }

    /**
     * Get the string for the actual bar based on the current length
     *
     * @param integer $length
     *
     * @return string
     */
    protected function getBar($length)
    {
        $bar     = str_repeat('=', $length);
        $padding = str_repeat(' ', $this->getBarStrLen() - $length);

        return "{$bar}>{$padding}";
    }

    /**
     * Get the length of the bar string based on the width of the terminal window
     *
     * @return integer
     */
    protected function getBarStrLen()
    {
        if (!$this->bar_str_len) {
            // Subtract 10 because of the '> 100%' plus some padding, max 100
            $this->bar_str_len = min($this->util->width() - 10, 100);
        }

        return $this->bar_str_len;
    }

    /**
     * Format the percentage so it looks pretty
     *
     * @param integer $percentage
     * @return float
     */
    protected function percentageFormatted($percentage)
    {
        return round($percentage * 100) . '%';
    }

    /**
     * Format the label so it is positioned correctly
     *
     * @param string $label
     * @return string
     */
    protected function labelFormatted($label)
    {
        return "\n" . $this->util->cursor->startOfCurrentLine() . $this->util->cursor->deleteCurrentLine() . $label;
    }

    /**
     * Determine whether the progress bar has changed and we need to redrew
     *
     * @param string $percentage
     * @param string $label
     *
     * @return boolean
     */
    protected function shouldRedraw($percentage, $label)
    {
        return ($this->force_redraw || $percentage != $this->current_percentage || $label != $this->label);
    }
}
climate/src/TerminalObject/Dynamic/Padding.php000064400000006105147361032630015325 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

class Padding extends DynamicTerminalObject
{
    /**
     * The length that lines should be padded to
     *
     * @var integer $length
     */
    protected $length = 0;

    /**
     * The character(s) that should be used to pad
     *
     * @var string $char
     */
    protected $char = '.';


    /**
     * If they pass in a padding character, set the char
     *
     * @param int $length
     * @param string $char
     */
    public function __construct($length = null, $char = null)
    {
        if ($length !== null) {
            $this->length($length);
        }

        if (is_string($char)) {
            $this->char($char);
        }
    }

    /**
     * Set the character(s) that should be used to pad
     *
     * @param string $char
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Padding
     */
    public function char($char)
    {
        $this->char = $char;

        return $this;
    }

    /**
     * Set the length of the line that should be generated
     *
     * @param integer $length
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Padding
     */
    public function length($length)
    {
        $this->length = $length;

        return $this;
    }

    /**
     * Get the length of the line based on the width of the terminal window
     *
     * @return integer
     */
    protected function getLength()
    {
        if (!$this->length) {
            $this->length = $this->util->width();
        }

        return $this->length;
    }

    /**
     * Pad the content with the characters
     *
     * @param string $content
     *
     * @return string
     */
    protected function padContent($content)
    {
        if (strlen($this->char) > 0) {
            $length = $this->getLength();
            $padding_length = ceil($length / mb_strlen($this->char));

            $padding = str_repeat($this->char, $padding_length);
            $content .= mb_substr($padding, 0, $length - mb_strlen($content));
        }

        return $content;
    }

    /**
     * Output the content and pad to the previously defined length
     *
     * @param string $content
     *
     * @return \League\CLImate\TerminalObject\Dynamic\Padding
     */
    public function label($content)
    {
        // Handle long labels by splitting them across several lines
        $lines = [];
        $stop = mb_strlen($content);
        $width = $this->util->width();
        for ($i = 0; $i < $stop; $i += $width) {
            $lines[] = mb_substr($content, $i, $width);
        }
        $content = array_pop($lines);

        foreach ($lines as $line) {
            $this->output->write($this->parser->apply($line));
        }

        $content = $this->padContent($content);
        $content = $this->parser->apply($content);

        $this->output->sameLine();
        $this->output->write($content);

        return $this;
    }

    /**
     * Output result
     *
     * @param string $content
     */
    public function result($content)
    {
        $this->output->write($this->parser->apply(' ' . $content));
    }
}
climate/src/TerminalObject/Dynamic/DynamicTerminalObject.php000064400000001016147361032630020162 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Decorator\Parser\ParserImporter;
use League\CLImate\Settings\SettingsImporter;
use League\CLImate\Util\OutputImporter;
use League\CLImate\Util\UtilImporter;

/**
 * The dynamic terminal object doesn't adhere to the basic terminal object
 * contract, which is why it gets its own base class
 */

abstract class DynamicTerminalObject implements DynamicTerminalObjectInterface
{
    use SettingsImporter, ParserImporter, OutputImporter, UtilImporter;
}
climate/src/TerminalObject/Dynamic/DynamicTerminalObjectInterface.php000064400000001032147361032630022001 0ustar00<?php

namespace League\CLImate\TerminalObject\Dynamic;

use League\CLImate\Decorator\Parser\Parser;
use League\CLImate\Util\UtilFactory;

interface DynamicTerminalObjectInterface
{
    public function settings();

    /**
     * @param $setting
     * @return void
     */
    public function importSetting($setting);

    /**
     * @param \League\CLImate\Decorator\Parser\Parser $parser
     */
    public function parser(Parser $parser);

    /**
     * @param UtilFactory $util
     */
    public function util(UtilFactory $util);
}
climate/src/TerminalObject/Basic/Inline.php000064400000000423147361032630014627 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Inline extends Out
{
    /**
     * Check if this object requires a new line should be added after the output
     *
     * @return boolean
     */
    public function sameLine()
    {
        return true;
    }
}
climate/src/TerminalObject/Basic/Repeatable.php000064400000000532147361032630015456 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

abstract class Repeatable extends BasicTerminalObject
{
    /**
     * How many times the element should be repeated
     *
     * @var integer
     */
    protected $count;

    public function __construct($count = 1)
    {
        $this->count = (int) round(max((int) $count, 1));
    }
}
climate/src/TerminalObject/Basic/Columns.php000064400000011214147361032630015031 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

use League\CLImate\TerminalObject\Helper\StringLength;

class Columns extends BasicTerminalObject
{
    use StringLength;

    /**
     * Number of columns
     *
     * @var integer $column_count
     */
    protected $column_count;

    /**
     * Data to columnize
     *
     * @var array $data
     */
    protected $data;

    public function __construct($data, $column_count = null)
    {
        $this->data  = $data;
        $this->column_count = $column_count;
    }

    /**
     * Calculate the number of columns organize data
     *
     * @return array
     */
    public function result()
    {
        $keys      = array_keys($this->data);
        $first_key = reset($keys);

        return (!is_int($first_key)) ? $this->associativeColumns() : $this->columns();
    }

    /**
     * Get columns for a regular array
     *
     * @return array
     */
    protected function columns()
    {
        $this->data    = $this->setData();
        $column_widths = $this->getColumnWidths();
        $output        = [];
        $count         = count(reset($this->data));

        for ($i = 0; $i < $count; $i++) {
            $output[] = $this->getRow($i, $column_widths);
        }

        return $output;
    }

    /**
     * Re-configure the data into it's final form
     */
    protected function setData()
    {
        // If it's already an array of arrays, we're good to go
        if (is_array(reset($this->data))) {
            return $this->setArrayOfArraysData();
        }

        $column_width = $this->getColumnWidth($this->data);
        $row_count    = $this->getMaxRows($column_width);

        return array_chunk($this->data, $row_count);
    }

    /**
     * Re-configure an array of arrays into column arrays
     */
    protected function setArrayOfArraysData()
    {
        $this->setColumnCountViaArray($this->data);

        $new_data = array_fill(0, $this->column_count, []);

        foreach ($this->data as $items) {
            for ($i = 0; $i < $this->column_count; $i++) {
                $new_data[$i][] = (array_key_exists($i, $items)) ? $items[$i] : null;
            }
        }

        return $new_data;
    }

    /**
     * Get columns for an associative array
     *
     * @return array
     */
    protected function associativeColumns()
    {
        $column_width = $this->getColumnWidth(array_keys($this->data));
        $output       = [];

        foreach ($this->data as $key => $value) {
            $output[] = $this->pad($key, $column_width) . $value;
        }

        return $output;
    }

    /**
     * Get the row of data
     *
     * @param integer $key
     * @param array $column_widths
     *
     * @return string
     */
    protected function getRow($key, $column_widths)
    {
        $row = [];

        for ($j = 0; $j < $this->column_count; $j++) {
            if (isset($this->data[$j]) && array_key_exists($key, $this->data[$j])) {
                $row[] = $this->pad($this->data[$j][$key], $column_widths[$j]);
            }
        }

        return trim(implode('', $row));
    }

    /**
     * Get the standard column width
     *
     * @param array $data
     *
     * @return integer
     */
    protected function getColumnWidth($data)
    {
        // Return the maximum width plus a buffer
        return $this->maxStrLen($data) + 5;
    }

    /**
     * Get an array of each column's width
     *
     * @return array
     */
    protected function getColumnWidths()
    {
        $column_widths = [];

        for ($i = 0; $i < $this->column_count; $i++) {
            if (!isset($this->data[$i])) {
                $column_widths[] = 0;
                continue;
            }
            $column_widths[] = $this->getColumnWidth($this->data[$i]);
        }

        return $column_widths;
    }

    /**
     * Set the count property
     *
     * @param integer $column_width
     */
    protected function setColumnCount($column_width)
    {
        $this->column_count = (int) floor($this->util->width() / $column_width);
    }

    /**
     * Set the count property via an array
     *
     * @param array $items
     */
    protected function setColumnCountViaArray($items)
    {
        $counts = array_map(function ($arr) {
            return count($arr);
        }, $items);

        $this->column_count = max($counts);
    }

    /**
     * Get the number of rows per column
     *
     * @param integer $column_width
     *
     * @return integer
     */
    protected function getMaxRows($column_width)
    {
        if (!$this->column_count) {
            $this->setColumnCount($column_width);
        }

        return ceil(count($this->data) / $this->column_count);
    }
}
climate/src/TerminalObject/Basic/Table.php000064400000013566147361032630014454 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

use League\CLImate\Exceptions\InvalidArgumentException;
use League\CLImate\TerminalObject\Helper\StringLength;
use function get_object_vars;
use function is_array;
use function is_object;

class Table extends BasicTerminalObject
{
    use StringLength;

    /**
     * The data for the table, an array of (arrays|objects)
     *
     * @var array $data
     */
    protected $data           = [];

    /**
     * An array of the widths of each column in the table
     *
     * @var array $column_widths
     */
    protected $column_widths  = [];

    /**
     * The width of the table
     *
     * @var integer $table_width
     */
    protected $table_width    = 0;

    /**
     * The divider between table cells
     *
     * @var string $column_divider
     */
    protected $column_divider = ' | ';

    /**
     * The border to divide each row of the table
     *
     * @var string $border
     */
    protected $border;

    /**
     * The array of rows that will ultimately be returned
     *
     * @var array $rows
     */
    protected $rows           = [];

    /**
     * @var string $pregix A string to be output before each row is output.
     */
    private $prefix = "";


    public function __construct(array $data, $prefix = "")
    {
        $this->data = $this->getData($data);
        $this->prefix = $prefix;
    }


    /**
     * @param array $input
     *
     * @return array
     */
    private function getData(array $input)
    {
        $output = [];

        foreach ($input as $item) {
            if (is_object($item)) {
                $item = get_object_vars($item);
            }

            if (!is_array($item)) {
                throw new InvalidArgumentException("Invalid table data, you must pass an array of arrays or objects");
            }

            $output[] = $item;
        }

        return $output;
    }


    /**
     * Return the built rows
     *
     * @return array
     */
    public function result()
    {
        $this->column_widths = $this->getColumnWidths();
        $this->table_width   = $this->getWidth();
        $this->border        = $this->getBorder();

        $this->buildHeaderRow();

        foreach ($this->data as $columns) {
            $this->addLine($this->buildRow($columns));
            $this->addLine($this->border);
        }

        return $this->rows;
    }

    /**
     * Append a line to the output.
     *
     * @param string $line The line to output
     *
     * @return void
     */
    private function addLine($line)
    {
        $this->rows[] = $this->prefix . $line;
    }


    /**
     * Determine the width of the table
     *
     * @return integer
     */
    protected function getWidth()
    {
        $first_row = reset($this->data);
        $first_row = $this->buildRow($first_row);

        return $this->lengthWithoutTags($first_row);
    }

    /**
     * Get the border for each row based on the table width
     */
    protected function getBorder()
    {
        return (new Border())->length($this->table_width)->result();
    }

    /**
     * Check for a header row (if it's an array of associative arrays or objects),
     * if there is one, tack it onto the front of the rows array
     */
    protected function buildHeaderRow()
    {
        $this->addLine($this->border);

        $header_row = $this->getHeaderRow();
        if ($header_row) {
            $this->addLine($this->buildRow($header_row));
            $this->addLine((new Border)->char('=')->length($this->table_width)->result());
        }
    }

    /**
     * Get table row
     *
     * @param  mixed  $columns
     *
     * @return string
     */
    protected function buildRow($columns)
    {
        $row = [];

        foreach ($columns as $key => $column) {
            $row[] = $this->buildCell($key, $column);
        }

        $row = implode($this->column_divider, $row);

        return trim($this->column_divider . $row . $this->column_divider);
    }

    /**
     * Build the string for this particular table cell
     *
     * @param  mixed  $key
     * @param  string $column
     *
     * @return string
     */
    protected function buildCell($key, $column)
    {
        return  $this->pad($column, $this->column_widths[$key]);
    }

    /**
     * Get the header row for the table if it's an associative array or object
     *
     * @return mixed
     */
    protected function getHeaderRow()
    {
        $first_item = reset($this->data);

        $keys       = array_keys($first_item);
        $first_key  = reset($keys);

        // We have an associative array (probably), let's have a header row
        if (!is_int($first_key)) {
            return array_combine($keys, $keys);
        }

        return false;
    }

    /**
     * Determine the width of each column
     *
     * @return array
     */
    protected function getColumnWidths()
    {
        $first_row = reset($this->data);

        // Create an array with the columns as keys and values of zero
        $column_widths = $this->getDefaultColumnWidths($first_row);

        foreach ($this->data as $columns) {
            foreach ($columns as $key => $column) {
                $column_widths[$key] = $this->getCellWidth($column_widths[$key], $column);
            }
        }

        return $column_widths;
    }

    /**
     * Set up an array of default column widths
     *
     * @param array $columns
     *
     * @return array
     */
    protected function getDefaultColumnWidths(array $columns)
    {
        $widths = $this->arrayOfStrLens(array_keys($columns));

        return array_combine(array_keys($columns), $widths);
    }

    /**
     * Determine the width of the columns without tags
     *
     * @param array  $current_width
     * @param string $str
     *
     * @return integer
     */
    protected function getCellWidth($current_width, $str)
    {
        return max($current_width, $this->lengthWithoutTags($str));
    }
}
climate/src/TerminalObject/Basic/BasicTerminalObjectInterface.php000064400000001175147361032630021103 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

use League\CLImate\Decorator\Parser\Parser;
use League\CLImate\Util\UtilFactory;

interface BasicTerminalObjectInterface
{
    public function result();

    public function settings();

    /**
     * @param $setting
     * @return void
     */
    public function importSetting($setting);

    /**
     * @return boolean
     */
    public function sameLine();

    /**
     * @param \League\CLImate\Decorator\Parser\Parser $parser
     */
    public function parser(Parser $parser);

    /**
     * @param UtilFactory $util
     */
    public function util(UtilFactory $util);
}
climate/src/TerminalObject/Basic/Dump.php000064400000001034147361032630014315 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Dump extends BasicTerminalObject
{
    /**
     * The data to convert to JSON
     *
     * @var mixed $data
     */
    protected $data;

    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * Return the data as JSON
     *
     * @return string
     */
    public function result()
    {
        ob_start();

        var_dump($this->data);

        $result = ob_get_contents();

        ob_end_clean();

        return $result;
    }
}
climate/src/TerminalObject/Basic/Tab.php000064400000001050147361032630014114 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

/**
 * Tab class to enable tabs to be output without using the escape character.
 */
class Tab extends Repeatable
{
    /**
     * Check if this object requires a new line should be added after the output.
     *
     * @return boolean
     */
    public function sameLine()
    {
        return true;
    }

    /**
     * Return the relevant number of tab characters.
     *
     * @return string
     */
    public function result()
    {
        return str_repeat("\t", $this->count);
    }
}
climate/src/TerminalObject/Basic/Flank.php000064400000002507147361032630014451 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Flank extends BasicTerminalObject
{
    /**
     * The string that will be flanked
     *
     * @var string $str
     */
    protected $str;

    /**
     * The character(s) to repeat on either side of the string
     *
     * @var string $char
     */
    protected $char = '#';

    /**
     * How many times the character(s) should be repeated on either side
     *
     * @var integer $repeat
     */
    protected $repeat = 3;

    public function __construct($str, $char = null, $repeat = null)
    {
        $this->str = $str;

        $this->char($char)->repeat($repeat);
    }

    /**
     * Set the character(s) to repeat on either side
     *
     * @param string $char
     *
     * @return Flank
     */
    public function char($char)
    {
        $this->set('char', $char);

        return $this;
    }

    /**
     * Set the repeat of the flank character(s)
     *
     * @param integer $repeat
     *
     * @return Flank
     */
    public function repeat($repeat)
    {
        $this->set('repeat', $repeat);

        return $this;
    }

    /**
     * Return the flanked string
     *
     * @return string
     */
    public function result()
    {
        $flank = str_repeat($this->char, $this->repeat);

        return "{$flank} {$this->str} {$flank}";
    }
}
climate/src/TerminalObject/Basic/Br.php000064400000000373147361032640013761 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Br extends Repeatable
{
    /**
     * Return an empty string
     *
     * @return string
     */
    public function result()
    {
        return array_fill(0, $this->count, '');
    }
}
climate/src/TerminalObject/Basic/Draw.php000064400000001120147361032640014302 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

use League\CLImate\TerminalObject\Helper\Art;

class Draw extends BasicTerminalObject
{
    use Art;

    public function __construct($art)
    {
        // Add the default art directory
        $this->addDir(__DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . 'ASCII');

        $this->art = $art;
    }

    /**
     * Return the art
     *
     * @return array
     */
    public function result()
    {
        $file = $this->artFile($this->art);

        return $this->parse($file);
    }
}
climate/src/TerminalObject/Basic/Out.php000064400000000675147361032640014172 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Out extends BasicTerminalObject
{
    /**
     * The content to output
     *
     * @var string $content
     */
    protected $content;

    public function __construct($content)
    {
        $this->content = $content;
    }

    /**
     * Return the content to output
     *
     * @return string
     */
    public function result()
    {
        return $this->content;
    }
}
climate/src/TerminalObject/Basic/Clear.php000064400000000460147361032640014441 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Clear extends BasicTerminalObject
{
    /**
     * Clear the terminal
     *
     * @return string
     */
    public function result()
    {
        return "\e[H\e[2J";
    }

    public function sameLine()
    {
        return true;
    }
}
climate/src/TerminalObject/Basic/ClearLine.php000064400000001226147361032640015252 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

final class ClearLine extends Repeatable
{
    /**
     * Clear the lines.
     *
     * @return string
     */
    public function result()
    {
        $string = "";
        while ($this->count > 0) {
            $string .= $this->util->cursor->startOfCurrentLine();
            $string .= $this->util->cursor->deleteCurrentLine();
            --$this->count;

            $string .= $this->util->cursor->up();
        }

        $string .= $this->util->cursor->down();

        return $string;
    }


    /**
     * @inheritdoc
     */
    public function sameLine()
    {
        return true;
    }
}
climate/src/TerminalObject/Basic/Border.php000064400000002263147361032640014633 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Border extends BasicTerminalObject
{
    /**
     * The character to repeat for the border
     *
     * @var string $char
     */
    protected $char = '-';

    /**
     * The length of the border
     *
     * @var integer $length
     */
    protected $length;

    public function __construct($char = null, $length = null)
    {
        $this->char($char)->length($length);
    }

    /**
     * Set the character to repeat for the border
     *
     * @param string $char
     *
     * @return Border
     */
    public function char($char)
    {
        $this->set('char', $char);

        return $this;
    }

    /**
     * Set the length of the border
     *
     * @param integer $length
     *
     * @return Border
     */
    public function length($length)
    {
        $this->set('length', $length);

        return $this;
    }

    /**
     * Return the border
     *
     * @return string
     */
    public function result()
    {
        $length = $this->length ?: $this->util->width() ?: 100;
        $str    = str_repeat($this->char, $length);
        $str    = substr($str, 0, $length);

        return $str;
    }
}
climate/src/TerminalObject/Basic/Json.php000064400000000745147361032640014332 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

class Json extends BasicTerminalObject
{
    /**
     * The data to convert to JSON
     *
     * @var mixed $data
     */
    protected $data;

    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * Return the data as JSON
     *
     * @return string
     */
    public function result()
    {
        return json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    }
}
climate/src/TerminalObject/Basic/BasicTerminalObject.php000064400000001711147361032640017257 0ustar00<?php

namespace League\CLImate\TerminalObject\Basic;

use League\CLImate\Decorator\Parser\ParserImporter;
use League\CLImate\Settings\SettingsImporter;
use League\CLImate\Util\UtilImporter;

abstract class BasicTerminalObject implements BasicTerminalObjectInterface
{
    use SettingsImporter, ParserImporter, UtilImporter;

    /**
     * Set the property if there is a valid value
     *
     * @param string $key
     * @param string $value
     */
    protected function set($key, $value)
    {
        if (strlen($value)) {
            $this->$key = $value;
        }
    }

    /**
     * Get the parser for the current object
     *
     * @return \League\CLImate\Decorator\Parser\Parser
     */
    public function getParser()
    {
        return $this->parser;
    }

    /**
     * Check if this object requires a new line to be added after the output
     *
     * @return boolean
     */
    public function sameLine()
    {
        return false;
    }
}
climate/src/TerminalObject/Router/RouterInterface.php000064400000000765147361032640016763 0ustar00<?php

namespace League\CLImate\TerminalObject\Router;

interface RouterInterface
{
    /**
     * @param $class
     * @return string
     */
    public function path($class);

    /**
     * @param $class
     * @return boolean
     */
    public function exists($class);

    /**
     * @param $obj
     * @return null|\League\CLImate\TerminalObject\Dynamic\DynamicTerminalObject
     */
    public function execute($obj);

    /**
     * @return string
     */
    public function pathPrefix();
}
climate/src/TerminalObject/Router/BasicRouter.php000064400000001520147361032640016072 0ustar00<?php

namespace League\CLImate\TerminalObject\Router;

use League\CLImate\Util\Helper;
use League\CLImate\Util\OutputImporter;

class BasicRouter extends BaseRouter
{
    use OutputImporter;

    /**
     * @return string
     */
    public function pathPrefix()
    {
        return 'Basic';
    }

    /**
     * Execute a basic terminal object
     *
     * @param \League\CLImate\TerminalObject\Basic\BasicTerminalObject $obj
     * @return void
     */
    public function execute($obj)
    {
        $results = Helper::toArray($obj->result());

        $this->output->persist();

        foreach ($results as $result) {
            if ($obj->sameLine()) {
                $this->output->sameLine();
            }

            $this->output->write($obj->getParser()->apply($result));
        }

        $this->output->persist(false);
    }
}
climate/src/TerminalObject/Router/BaseRouter.php000064400000003466147361032640015736 0ustar00<?php

namespace League\CLImate\TerminalObject\Router;

abstract class BaseRouter implements RouterInterface
{
    protected $extensions = [];

    /**
     * Add a custom extension to CLImate
     *
     * @param string $key
     * @param string $class
     */
    public function addExtension($key, $class)
    {
        $this->extensions[$key] = $class;
    }

    /**
     * Get the full path for the class based on the key
     *
     * @param string $class
     *
     * @return string
     */
    public function path($class)
    {
        return $this->getExtension($class) ?: $this->getPath($this->shortName($class));
    }

    /**
     * Determines if the requested class is a
     * valid terminal object class
     *
     * @param  string  $class
     *
     * @return boolean
     */
    public function exists($class)
    {
        $class = $this->path($class);

        return (is_object($class) || class_exists($class));
    }

    /**
     * Get the full path for the terminal object class
     *
     * @param  string $class
     *
     * @return string
     */
    protected function getPath($class)
    {
        return 'League\CLImate\TerminalObject\\' . $this->pathPrefix() . '\\' . $class;
    }

    /**
     * Get an extension by its key
     *
     * @param string $key
     *
     * @return string|false Full class path to extension
     */
    protected function getExtension($key)
    {
        if (array_key_exists($key, $this->extensions)) {
            return $this->extensions[$key];
        }

        return false;
    }

    /**
     * Get the class short name
     *
     * @param string $name
     *
     * @return string
     */
    protected function shortName($name)
    {
        $name = str_replace('_', ' ', $name);
        $name = ucwords($name);

        return str_replace(' ', '', $name);
    }
}
climate/src/TerminalObject/Router/Router.php000064400000007746147361032640015150 0ustar00<?php

namespace League\CLImate\TerminalObject\Router;

use League\CLImate\Decorator\Parser\ParserImporter;
use League\CLImate\Settings\Manager;
use League\CLImate\Settings\SettingsImporter;
use League\CLImate\Util\OutputImporter;
use League\CLImate\Util\UtilImporter;

class Router
{
    use ParserImporter, SettingsImporter, OutputImporter, UtilImporter;

    /**
     * An instance of the Settings Manager class
     *
     * @var \League\CLImate\Settings\Manager $settings;
     */
    protected $settings;

    /**
     * An instance of the Dynamic Router class
     *
     * @var \League\CLImate\TerminalObject\Router\DynamicRouter $dynamic;
     */
    protected $dynamic;

    /**
     * An instance of the Basic Router class
     *
     * @var \League\CLImate\TerminalObject\Router\BasicRouter $basic;
     */
    protected $basic;

    public function __construct(DynamicRouter $dynamic = null, BasicRouter $basic = null)
    {
        $this->dynamic = $dynamic ?: new DynamicRouter();
        $this->basic   = $basic ?: new BasicRouter();
    }

    /**
     * Register a custom class with the router
     *
     * @param string $key
     * @param string $class
     */
    public function addExtension($key, $class)
    {
        $extension = new ExtensionCollection($key, $class);

        foreach ($extension->collection() as $obj_type => $collection) {
            foreach ($collection as $obj_key => $obj_class) {
                $this->{$obj_type}->addExtension($obj_key, $obj_class);
            }
        }
    }

    /**
     * Check if the name matches an existing terminal object
     *
     * @param string $name
     *
     * @return boolean
     */
    public function exists($name)
    {
        return ($this->basic->exists($name) || $this->dynamic->exists($name));
    }

    /**
     * Execute a terminal object using given arguments
     *
     * @param string $name
     * @param mixed  $arguments
     *
     * @return null|\League\CLImate\TerminalObject\Basic\BasicTerminalObjectInterface
     */
    public function execute($name, $arguments)
    {
        $router = $this->getRouter($name);

        $router->output($this->output);

        $obj = $this->getObject($router, $name, $arguments);

        $obj->parser($this->parser);
        $obj->util($this->util);

        // If the object needs any settings, import them
        foreach ($obj->settings() as $obj_setting) {
            $setting = $this->settings->get($obj_setting);

            if ($setting) {
                $obj->importSetting($setting);
            }
        }

        return $router->execute($obj);
    }

    /**
     * Get the object whether it's a string or already instantiated
     *
     * @param \League\CLImate\TerminalObject\Router\RouterInterface $router
     * @param string $name
     * @param array $arguments
     *
     * @return \League\CLImate\TerminalObject\Dynamic\DynamicTerminalObjectInterface|\League\CLImate\TerminalObject\Basic\BasicTerminalObjectInterface
     */
    protected function getObject($router, $name, $arguments)
    {
        $obj = $router->path($name);

        if (is_string($obj)) {
            $obj = (new \ReflectionClass($obj))->newInstanceArgs($arguments);
        }

        if (method_exists($obj, 'arguments')) {
            call_user_func_array([$obj, 'arguments'], $arguments);
        }

        return $obj;
    }

    /**
     * Determine which type of router we are using and return it
     *
     * @param string $name
     *
     * @return \League\CLImate\TerminalObject\Router\RouterInterface|null
     */
    protected function getRouter($name)
    {
        if ($this->basic->exists($name)) {
            return $this->basic;
        }

        if ($this->dynamic->exists($name)) {
            return $this->dynamic;
        }
    }

    /**
     * Set the settings property
     *
     * @param  \League\CLImate\Settings\Manager $settings
     *
     * @return Router
     */
    public function settings(Manager $settings)
    {
        $this->settings = $settings;

        return $this;
    }
}
climate/src/TerminalObject/Router/ExtensionCollection.php000064400000007525147361032640017653 0ustar00<?php

namespace League\CLImate\TerminalObject\Router;

use League\CLImate\Exceptions\InvalidArgumentException;
use League\CLImate\Exceptions\UnexpectedValueException;
use League\CLImate\Util\Helper;

class ExtensionCollection
{
    /**
     * @var array collection
     */
    protected $collection = ['basic' => [], 'dynamic' => []];

    /**
     * @var string $basic_interface
     */
    protected $basic_interface = 'League\CLImate\TerminalObject\Basic\BasicTerminalObjectInterface';

    /**
     * @var string $dynamic_interface
     */
    protected $dynamic_interface = 'League\CLImate\TerminalObject\Dynamic\DynamicTerminalObjectInterface';

    public function __construct($key, $class)
    {
        $this->createCollection($key, $class);
    }

    public function collection()
    {
        return $this->collection;
    }

    /**
     * Create the collection from the key/class
     *
     * @param string $original_key
     * @param string|object|array $original_class
     *
     * @return void
     */
    protected function createCollection($original_key, $original_class)
    {
        $collection = $this->convertToArray($original_key, $original_class);

        foreach ($collection as $key => $class) {
            $this->validateExtension($class);
            $this->collection[$this->getType($class)][$this->getKey($key, $class)] = $class;
        }
    }

    /**
     * Convert the given class and key to an array of classes
     *
     * @param string|object|array $class
     * @param string $key Optional custom key instead of class name
     *
     * @return array
     */
    protected function convertToArray($key, $class)
    {
        if (is_array($class)) {
            return $class;
        }

        return [$this->getKey($key, $class) => $class];
    }

    /**
     * Ensure that the extension is valid
     *
     * @param string|object|array $class
     */
    protected function validateExtension($class)
    {
        $this->validateClassExists($class);
        $this->validateClassImplementation($class);
    }

    /**
     * @param string|object $class
     *
     * @throws UnexpectedValueException if extension class does not exist
     */
    protected function validateClassExists($class)
    {
        if (is_string($class) && !class_exists($class)) {
            throw new UnexpectedValueException('Class does not exist: ' . $class);
        }
    }

    /**
     * @param string|object $class
     *
     * @throws InvalidArgumentException if extension class does not implement either Dynamic or Basic interface
     */
    protected function validateClassImplementation($class)
    {
        $str_class = is_string($class);

        $valid_implementation = (is_a($class, $this->basic_interface, $str_class)
                                    || is_a($class, $this->dynamic_interface, $str_class));

        if (!$valid_implementation) {
            throw new InvalidArgumentException('Class must implement either '
                                    . $this->basic_interface . ' or ' . $this->dynamic_interface);
        }
    }

    /**
     * Determine the extension key based on the class
     *
     * @param string|null $key
     * @param string|object $class
     *
     * @return string
     */
    protected function getKey($key, $class)
    {
        if ($key === null || !is_string($key)) {
            $class_path = (is_string($class)) ? $class : get_class($class);

            $key = explode('\\', $class_path);
            $key = end($key);
        }

        return Helper::snakeCase($key);
    }

    /**
     * Get the type of class the extension implements
     *
     * @param string|object $class
     *
     * @return string 'basic' or 'dynamic'
     */
    protected function getType($class)
    {
        if (is_a($class, $this->basic_interface, is_string($class))) {
            return 'basic';
        }

        return 'dynamic';
    }
}
climate/src/TerminalObject/Router/DynamicRouter.php000064400000001165147361032640016442 0ustar00<?php

namespace League\CLImate\TerminalObject\Router;

use League\CLImate\Util\OutputImporter;

class DynamicRouter extends BaseRouter
{
    use OutputImporter;

    /**
     * @return string
     */
    public function pathPrefix()
    {
        return 'Dynamic';
    }

    /**
     * Execute a dynamic terminal object using given arguments
     *
     * @param \League\CLImate\TerminalObject\Dynamic\DynamicTerminalObject $obj
     *
     * @return \League\CLImate\TerminalObject\Dynamic\DynamicTerminalObject
     */
    public function execute($obj)
    {
        $obj->output($this->output);

        return $obj;
    }
}
climate/src/TerminalObject/Helper/StringLength.php000064400000004652147361032640016230 0ustar00<?php

namespace League\CLImate\TerminalObject\Helper;

trait StringLength
{
    /**
     * Tags the should not be ultimately considered
     * when calculating any string lengths
     *
     * @var array $ignore_tags
     */
    protected $ignore_tags = [];

    /**
     * Set the ignore tags property
     */
    protected function setIgnoreTags()
    {
        if (!count($this->ignore_tags)) {
            $this->ignore_tags = array_keys($this->parser->tags->all());
        }
    }

    /**
     * Determine the length of the string without any tags
     *
     * @param  string  $str
     *
     * @return integer
     */
    protected function lengthWithoutTags($str)
    {
        $this->setIgnoreTags();

        return mb_strwidth($this->withoutTags($str), 'UTF-8');
    }

    /**
     * Get the string without the tags that are to be ignored
     *
     * @param  string $str
     *
     * @return string
     */
    protected function withoutTags($str)
    {
        $this->setIgnoreTags();

        return str_replace($this->ignore_tags, '', $str);
    }

    /**
     * Apply padding to a string
     *
     * @param string $str
     * @param string $final_length
     * @param string $padding_side
     *
     * @return string
     */
    protected function pad($str, $final_length, $padding_side = 'right')
    {
        $padding = $final_length - $this->lengthWithoutTags($str);

        if ($padding_side == 'left') {
            return str_repeat(' ', $padding) . $str;
        }

        return $str . str_repeat(' ', $padding);
    }

    /**
     * Apply padding to an array of strings
     *
     * @param  array $arr
     * @param  integer $final_length
     * @param string $padding_side
     *
     * @return array
     */
    protected function padArray($arr, $final_length, $padding_side = 'right')
    {
        foreach ($arr as $key => $value) {
            $arr[$key] = $this->pad($value, $final_length, $padding_side);
        }

        return $arr;
    }

    /**
     * Find the max string length in an array
     *
     * @param array $arr
     * @return int
     */
    protected function maxStrLen(array $arr)
    {
        return max($this->arrayOfStrLens($arr));
    }

    /**
     * Get an array of the string lengths from an array of strings
     *
     * @param array $arr
     * @return array
     */
    protected function arrayOfStrLens(array $arr)
    {
        return array_map([$this, 'lengthWithoutTags'], $arr);
    }
}
climate/src/TerminalObject/Helper/Sleeper.php000064400000001314147361032640015207 0ustar00<?php

namespace League\CLImate\TerminalObject\Helper;

class Sleeper implements SleeperInterface
{
    /**
     * The default length of the sleep
     *
     * @var int|float $speed
     */
    protected $speed = 50000;

    /**
     * Set the speed based on a percentage (50% slower, 200% faster, etc)
     *
     * @param int|float $percentage
     *
     * @return float
     */
    public function speed($percentage)
    {
        if (is_numeric($percentage) && $percentage > 0) {
            $this->speed *= (100 / $percentage);
        }

        return $this->speed;
    }

    /**
     * Sleep for the specified amount of time
     */
    public function sleep()
    {
        usleep($this->speed);
    }
}
climate/src/TerminalObject/Helper/SleeperInterface.php000064400000000321147361032640017025 0ustar00<?php

namespace League\CLImate\TerminalObject\Helper;

interface SleeperInterface
{
    /**
     * @param int|float $percentage
     */
    public function speed($percentage);

    public function sleep();
}
climate/src/TerminalObject/Helper/Art.php000064400000007350147361032640014344 0ustar00<?php

namespace League\CLImate\TerminalObject\Helper;

use League\CLImate\Exceptions\UnexpectedValueException;

use function preg_quote;

trait Art
{
    /**
     * The directories we should be looking for art in
     *
     * @var array $art_dirs
     */
    protected $art_dirs = [];

    /**
     * The default art if we can't find what the user requested
     *
     * @var string $default_art
     */
    protected $default_art = '404';

    /**
     * The art requested by the user
     *
     * @var string $art
     */
    protected $art = '';

    /**
     * Specify which settings Draw needs to import
     *
     * @return array
     */
    public function settings()
    {
        return ['Art'];
    }

    /**
     * Import the Art setting (any directories the user added)
     *
     * @param \League\CLImate\Settings\Art $setting
     */
    public function importSettingArt($setting)
    {
        foreach ($setting->dirs as $dir) {
            $this->addDir($dir);
        }
    }

    /**
     * Add a directory to search for art in
     *
     * @param string $dir
     */
    protected function addDir($dir)
    {
        // Add any additional directories to the top of the array
        // so that the user can override art
        array_unshift($this->art_dirs, rtrim($dir, \DIRECTORY_SEPARATOR));

        // Keep the array clean
        $this->art_dirs = array_unique($this->art_dirs);
        $this->art_dirs = array_filter($this->art_dirs);
        $this->art_dirs = array_values($this->art_dirs);
    }

    /**
     * Find a valid art path
     *
     * @param string $art
     *
     * @return array
     */
    protected function artDir($art)
    {
        return $this->fileSearch($art, preg_quote(\DIRECTORY_SEPARATOR) . '*.*');
    }

    /**
     * Find a valid art path
     *
     * @param string $art
     *
     * @return string
     */
    protected function artFile($art)
    {
        $files = $this->fileSearch($art, '(\.[^' . preg_quote(\DIRECTORY_SEPARATOR) . ']*)?$');

        if (count($files) === 0) {
            $this->addDir(__DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . 'ASCII');
            $files = $this->fileSearch($this->default_art, '.*');
        }

        if (count($files) === 0) {
            throw new UnexpectedValueException("Unable to find an art file with the name '{$art}'");
        }

        return reset($files);
    }

    /**
     * Find a set of files in the current art directories
     * based on a pattern
     *
     * @param string $art
     * @param string $pattern
     *
     * @return array
     */
    protected function fileSearch($art, $pattern)
    {
        foreach ($this->art_dirs as $dir) {
            $directory_iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));

            $paths = [];
            $regex = '~' . preg_quote($art) . $pattern . '~';

            foreach ($directory_iterator as $file) {
                if ($file->isDir()) {
                    continue;
                }

                // Look for anything that has the $art filename
                if (preg_match($regex, $file)) {
                    $paths[] = $file->getPathname();
                }
            }

            asort($paths);

            // If we've got one, no need to look any further
            if (!empty($paths)) {
                return $paths;
            }
        }

        return [];
    }

    /**
     * Parse the contents of the file and return each line
     *
     * @param string $path
     *
     * @return array
     */
    protected function parse($path)
    {
        $output = file_get_contents($path);
        $output = explode("\n", $output);
        $output = array_map('rtrim', $output);

        return $output;
    }
}
climate/README.md000064400000003416147361032640007453 0ustar00<p align="center"><img src="http://climate.thephpleague.com/img/CLImate_Blink.gif" width="300" alt="CLImate" /></p>

[![Latest Version](https://img.shields.io/github/tag/thephpleague/climate.svg?style=flat&label=release)](https://github.com/thephpleague/climate/tags)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE.md)
[![Build Status](https://img.shields.io/travis/thephpleague/climate/master.svg?style=flat)](https://travis-ci.org/thephpleague/climate)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/climate.svg?style=flat)](https://scrutinizer-ci.com/g/thephpleague/climate/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/climate.svg?style=flat)](https://scrutinizer-ci.com/g/thephpleague/climate)
[![Total Downloads](https://img.shields.io/packagist/dt/league/climate.svg?style=flat)](https://packagist.org/packages/league/climate)

Running PHP from the command line? CLImate is your new best bud.

CLImate allows you to easily output colored text, special formats, and more.


## Installation

The recommended method of installing this library is via [Composer](https://getcomposer.org/).

Run the following command from your project root:

```bash
$ composer require league/climate
```


## Usage

```php
require_once __DIR__ . "/vendor/autoload.php";

$climate = new \League\CLImate\CLImate;

$climate->red('Whoa now this text is red.');
$climate->blue('Blue? Wow!');
```

_Read more at https://climate.thephpleague.com/_  


## Credits

This library was created by [Joe Tannenbaum](https://joe.codes/).  
It is currently maintained and developed by [Craig Duncan](https://twitter.com/duncan3dc).  
Much love to [Damian Makki](https://dribbble.com/damianmakki) for the logo.    
climate/CHANGELOG.md000064400000014361147361032640010006 0ustar00Changelog
=========

--------

## 3.5.2 - 2019-12-01

### Fixed

* [Checkbox] Ensure we can output when the terminal width is unknown. [#156](https://github.com/thephpleague/climate/pull/156)

--------

## 3.5.1 - 2019-11-24

### Fixed

* [Art] Be stricter about matching filenames when looking for art. [#155](https://github.com/thephpleague/climate/issues/155)
* [Support] Provide better support for Windows using GitHub actions.

--------

## 3.5.0 - 2019-02-10

### Added

* [Arguments] Multiple arguments (eg `cli -d foo=bar -d baz=qux`) available via `$climate->arguments->getArray("d")`. [#111](https://github.com/thephpleague/climate/pull/111)
* [Arguments] A new method (`Argument::values()`) replaces the deprecated `valueArray()` method.
* [Spinner] Add an indeterminate progress spinner.
* [Exceptions] All exception now implement `League\CLImate\Exceptions\Exception`.
* [Basic] Allow single lines to be cleared using `$climate->clearLine()`. [#145](https://github.com/thephpleague/climate/issues/145).

### Fixed

* [Arguments] Mixing arguments with/without option values. [#122](https://github.com/thephpleague/climate/issues/122).
* [Output] Prevent `tput` output from being sent to the terminal.
* [Confirm] Accept more reasonable options for yes/no. [#126](https://github.com/thephpleague/climate/issues/126).
* [Radio] Allow ENTER to be used to choose an option. [#140](https://github.com/thephpleague/climate/issues/140).
* [Output] Improve the checks for ANSI color checks. [#128](https://github.com/thephpleague/climate/issues/128) / [#129](https://github.com/thephpleague/climate/issues/129).
* [Art] Only match filenames when looking for art. [#130](https://github.com/thephpleague/climate/issues/130)

### Changed

* [Support] Dropped support for PHP 5.6 and PHP 7.0

--------

## 3.4.1 - 2018-04-29

### Fixed

* [Json] Don't escape slashes when outputting JSON. [#121](https://github.com/thephpleague/climate/pull/121)

--------

## 3.4.0 - 2018-04-28

### Added

* [Logger] Added a Logger class to use CLImate as a PSR-3 logger.

--------

## 3.3.0 - 2018-04-20

### Fixed

* Ensure multibyte strings are supported everywhere.
* Improved support for IDE assistance when using method chaining. [#102](https://github.com/thephpleague/climate/pull/102)
* [Art] Improve handling of missing files. [#114](https://github.com/thephpleague/climate/issues/114)
* [Input] Correct the usage of `defaultTo()` with `accept()`. [#104](https://github.com/thephpleague/climate/pull/104)
* [Windows] Fixed the terminal width detection. [#64](https://github.com/thephpleague/climate/pull/64)

### Added

* [Table] Add support for a prefix argument for each row. [#51](https://github.com/thephpleague/climate/issues/51)
* [Progress] Added an `each()` method. [#112](https://github.com/thephpleague/climate/pull/112)

### Changed

* [Support] Add support for PHP 7.2
* [Support] Drop support for PHP 5.4
* [Support] Drop support for PHP 5.5
* [Support] Drop support for HHVM.
* Suggest the symfony polyfill library is `ext-mbstring` is not available. [#110](https://github.com/thephpleague/climate/pull/110)

--------

## 3.2.4 - 2016-10-30

### Fixed

* [Progres] Allow labels to be shown/hidden on each iteration. [#98](https://github.com/thephpleague/climate/pull/98)

--------

## 3.2.3 - 2016-10-17

### Added

* [Support] Added support for PHP 7.1

--------

## 3.2.2 - 2016-07-18

### Fixed

* [Art] Allow code to be used in a phar. [#86](https://github.com/thephpleague/climate/pull/86)

--------

## 3.2.1 - 2016-04-05

### Added

* [Arguments] Add a `trailing()` method to get any trailing arguments.
* [Progress] Added a `forceRedraw()` method. [#72](https://github.com/thephpleague/climate/issues/72)

### Fixed

* [Checkbox] Don't cancel out the formatting for the first checkbox. [#77](https://github.com/thephpleague/climate/issues/77)
* [Padding] Ensure formatting is handled. [#78](https://github.com/thephpleague/climate/issues/78)
* [Columns] Prevent error when less items than columns are passed. [#75](https://github.com/thephpleague/climate/pull/75)

--------

## 3.2.0 - 2015-08-13

### Added
- Multi-line support for `input` method [https://github.com/thephpleague/climate/pull/67](https://github.com/thephpleague/climate/pull/67)
- `extend` method for _much_ easier extending of CLImate

### Fixed
- Unnecessary progress bar re-drawing when the output hadn't changed [https://github.com/thephpleague/climate/pull/69](https://github.com/thephpleague/climate/pull/69)
- Progress label no longer removed once progress reaches 100% 
- Non-prefixed paramaters for `arguments` method now show in usage description [https://github.com/thephpleague/climate/issues/65](https://github.com/thephpleague/climate/issues/65)

## 3.1.1 - 2015-05-01

### Fixed
- Windows support added for `password` thanks to @Seldaek and [seld/cli-prompt](https://packagist.org/packages/seld/cli-prompt)

## 3.1.0 - 2015-04-30

### Added
- `password` prompt
- `checkboxes` prompt
- `radio` prompt
- 'file' as output option

## 3.0.0 - 2015-03-01

### Changed

- Custom output writers are added simply via the `output` property on CLImate now, as opposed to the immense amount of scaffolding required before

### Added

- Argument parsing
- StdErr output
- Buffer output
- `animate` method for running ASCII animations in the terminal. Because it's fun.
- Input now bolds the default response if it exists

## 2.6.1 - 2015-01-18

### Fixed

- Added `forceAnsiOn` and `forceAnsiOff` methods to address systems that were not identified correctly

## 2.6.0 - 2015-01-07

### Added

- Allow for passing an array of arrays into `columns` method
- `tab` method, for indenting text
- `padding` method, for padding strings to an equal width with a character
- `League\CLImate\TerminalObject\Repeatable` for repeatable objects such as `tab` and `br`
- `League\CLImate\Decorator\Parser\Ansi` and `League\CLImate\Decorator\Parser\NonAnsi`
- Factories:
    + `League\CLImate\Decorator\Parser\ParserFactory`
    + `League\CLImate\Util\System\SystemFactory`
- Terminal Objects now are appropriately namespaced as `Basic` or `Dynamic`
- Readers and Writers are appropriately namespaced as such under `League\CLImate\Util`

### Fixed

- Labels for `advance` method
- Non-ansi terminals will now have plaintext output instead of jumbled characters
- `border` method now default to full terminal width
climate/LICENSE.md000064400000002071147361032640007574 0ustar00The MIT License (MIT)

Copyright (c) 2014 Joe Tannenbaum

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
climate/package.json000064400000000226147361032640010456 0ustar00{
  "dependencies": {},
  "repository": {},
  "devDependencies": {
    "gulp": "^3.8.8"
  },
  "dependencies": {
    "laravel-elixir": "^2.0.0"
  }
}
climate/.styleci.yml000064400000000034147361032640010442 0ustar00preset: psr2

linting: true
openapi-psr7-validator/.githooks/pre-commit000064400000003740147361032640014771 0ustar00#!/bin/sh

#########################
#                       #
#     Initializing      #
#                       #
#########################

PHPCS_BIN=./vendor/bin/phpcs
PHPCS_LOG=./.phpcs-report.txt
PHPCBF_BIN=./vendor/bin/phpcbf

# Check for PHPCS / PHPCBF
if [[ ! -x ${PHPCS_BIN} ]]; then
    echo "[PRE-COMMIT] PHP CodeSniffer is not installed locally."
    echo "[PRE-COMMIT] Please run 'composer install' or check the path: $PHPCS_BIN"
    exit 1
fi

if [[ ! -x ${PHPCBF_BIN} ]]; then
    echo "[PRE-COMMIT] PHP Code Beautifier and Fixer is not installed locally."
    echo "[PRE-COMMIT] Please run 'composer install' or check the path: $PHPCBF_BIN"
    exit 1
fi


#########################
#                       #
#       Starting        #
#                       #
#########################

# All files in staging area (no deletions)

PROJECT=$(git rev-parse --show-toplevel)

    # Coding Standards

    echo "[PRE-COMMIT] Checking PHPCS..."

    # You can change your PHPCS command here
    ${PHPCS_BIN} -n ${FILES} &> /dev/null

    if [[ $? != 0 ]]
    then
        echo "[PRE-COMMIT] Coding standards errors have been detected."
        echo "[PRE-COMMIT] Running PHP Code Beautifier and Fixer..."

        # Check all codebase according to config file (not only staged files)
        ${PHPCBF_BIN} -qn  &> /dev/null

        echo "[PRE-COMMIT] Checking PHPCS again..."

        # You can change your PHPCS command here
        ${PHPCS_BIN} -n --report-file=${PHPCS_LOG}  &> /dev/null

        if [[ $? != 0 ]]
        then
            echo "[PRE-COMMIT] PHP Code Beautifier and Fixer wasn't able to solve all problems."
            echo "[PRE-COMMIT] See log at ${PHPCS_LOG}"
            exit 1
        fi

        echo "[PRE-COMMIT] All errors are fixed automatically."

        # stage and commit any changed files
        STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR HEAD)
        git add ${STAGED_FILES}
    else
        echo "[PRE-COMMIT] No errors found."
    fi

exit $?
openapi-psr7-validator/src/PSR7/SchemaFactory/JsonFileFactory.php000064400000000565147361032640020713 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\SchemaFactory;

use cebe\openapi\Reader;
use cebe\openapi\spec\OpenApi;

use function realpath;

final class JsonFileFactory extends FileFactory
{
    public function createSchema(): OpenApi
    {
        return Reader::readFromJsonFile(
            realpath($this->getFilename())
        );
    }
}
openapi-psr7-validator/src/PSR7/SchemaFactory/JsonFactory.php000064400000000742147361032640020110 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\SchemaFactory;

use cebe\openapi\Reader;
use cebe\openapi\ReferenceContext;
use cebe\openapi\spec\OpenApi;

final class JsonFactory extends StringFactory
{
    public function createSchema(): OpenApi
    {
        /** @var OpenApi $schema */
        $schema = Reader::readFromJson($this->getContent());

        $schema->resolveReferences(new ReferenceContext($schema, '/'));

        return $schema;
    }
}
openapi-psr7-validator/src/PSR7/SchemaFactory/YamlFactory.php000064400000000676147361032640020107 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\SchemaFactory;

use cebe\openapi\Reader;
use cebe\openapi\ReferenceContext;
use cebe\openapi\spec\OpenApi;

final class YamlFactory extends StringFactory
{
    public function createSchema(): OpenApi
    {
        $schema = Reader::readFromYaml($this->getContent());

        $schema->resolveReferences(new ReferenceContext($schema, '/'));

        return $schema;
    }
}
openapi-psr7-validator/src/PSR7/SchemaFactory/StringFactory.php000064400000001111147361032640020434 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\SchemaFactory;

use League\OpenAPIValidation\PSR7\CacheableSchemaFactory;

use function hash;

abstract class StringFactory implements CacheableSchemaFactory
{
    /** @var string */
    private $content;

    public function __construct(string $content)
    {
        $this->content = $content;
    }

    public function getCacheKey(): string
    {
        return 'openapi_' . hash('crc32b', $this->getContent());
    }

    protected function getContent(): string
    {
        return $this->content;
    }
}
openapi-psr7-validator/src/PSR7/SchemaFactory/YamlFileFactory.php000064400000000565147361032640020704 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\SchemaFactory;

use cebe\openapi\Reader;
use cebe\openapi\spec\OpenApi;

use function realpath;

final class YamlFileFactory extends FileFactory
{
    public function createSchema(): OpenApi
    {
        return Reader::readFromYamlFile(
            realpath($this->getFilename())
        );
    }
}
openapi-psr7-validator/src/PSR7/SchemaFactory/FileFactory.php000064400000001246147361032640020056 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\SchemaFactory;

use League\OpenAPIValidation\PSR7\CacheableSchemaFactory;
use Webmozart\Assert\Assert;

use function crc32;
use function realpath;

abstract class FileFactory implements CacheableSchemaFactory
{
    /** @var string */
    private $filename;

    public function __construct(string $filename)
    {
        Assert::file($filename);

        $this->filename = $filename;
    }

    public function getCacheKey(): string
    {
        return 'openapi_' . crc32(realpath($this->getFilename()));
    }

    protected function getFilename(): string
    {
        return $this->filename;
    }
}
openapi-psr7-validator/src/PSR7/SchemaFactory/PrecreatedSchemaFactory.php000064400000000716147361032640022377 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\SchemaFactory;

use cebe\openapi\spec\OpenApi;
use League\OpenAPIValidation\PSR7\SchemaFactory;

final class PrecreatedSchemaFactory implements SchemaFactory
{
    /** @var OpenApi */
    private $schema;

    public function __construct(OpenApi $schema)
    {
        $this->schema = $schema;
    }

    public function createSchema(): OpenApi
    {
        return $this->schema;
    }
}
openapi-psr7-validator/src/PSR7/ReusableSchema.php000064400000000262147361032640015777 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;

interface ReusableSchema
{
    public function getSchema(): OpenApi;
}
openapi-psr7-validator/src/PSR7/ServerRequestValidator.php000064400000007570147361032640017612 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use League\OpenAPIValidation\PSR7\Exception\MultipleOperationsMismatchForRequest;
use League\OpenAPIValidation\PSR7\Exception\NoOperation;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\Validators\BodyValidator\BodyValidator;
use League\OpenAPIValidation\PSR7\Validators\CookiesValidator\CookiesValidator;
use League\OpenAPIValidation\PSR7\Validators\HeadersValidator;
use League\OpenAPIValidation\PSR7\Validators\PathValidator;
use League\OpenAPIValidation\PSR7\Validators\QueryArgumentsValidator;
use League\OpenAPIValidation\PSR7\Validators\SecurityValidator;
use League\OpenAPIValidation\PSR7\Validators\ValidatorChain;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

use function count;
use function strtolower;

class ServerRequestValidator implements ReusableSchema
{
    /** @var OpenApi */
    protected $openApi;
    /** @var MessageValidator */
    protected $validator;

    public function __construct(OpenApi $schema)
    {
        $this->openApi   = $schema;
        $finder          = new SpecFinder($this->openApi);
        $this->validator = new ValidatorChain(
            new HeadersValidator($finder),
            new CookiesValidator($finder),
            new BodyValidator($finder),
            new QueryArgumentsValidator($finder),
            new PathValidator($finder),
            new SecurityValidator($finder)
        );
    }

    public function getSchema(): OpenApi
    {
        return $this->openApi;
    }

    /**
     * @return OperationAddress which matched the Request
     *
     * @throws ValidationFailed
     */
    public function validate(ServerRequestInterface $serverRequest): OperationAddress
    {
        $path   = $serverRequest->getUri()->getPath();
        $method = strtolower($serverRequest->getMethod());

        // 0. Find matching operations
        // If there is only one - then proceed with checking
        // If there are multiple candidates, then check each one, if all fail - we don't know which one supposed to be the one, so we need to throw an exception like
        // "This request matched operations A,B and C, but mismatched its schemas."
        $matchingOperationsAddrs = $this->findMatchingOperations($serverRequest);

        if (! $matchingOperationsAddrs) {
            throw NoOperation::fromPathAndMethod($path, $method);
        }

        // Single match is the most desirable variant, because we reduce ambiguity down to zero
        if (count($matchingOperationsAddrs) === 1) {
            $this->validator->validate($matchingOperationsAddrs[0], $serverRequest);

            return $matchingOperationsAddrs[0];
        }

        // there are multiple matching operations, this is bad, because if none of them match the message
        // then we cannot say reliably which one intended to match
        foreach ($matchingOperationsAddrs as $matchedAddr) {
            try {
                $this->validator->validate($matchedAddr, $serverRequest);

                return $matchedAddr; // Good, operation matched and request is valid against it, stop here
            } catch (Throwable $e) {
                // that operation did not match
            }
        }

        // no operation matched at all...
        throw MultipleOperationsMismatchForRequest::fromMatchedAddrs($matchingOperationsAddrs);
    }

    /**
     * Check the openapi spec and find matching operations(path+method)
     * This should consider path parameters as well
     * "/users/12" should match both ["/users/{id}", "/users/{group}"]
     *
     * @return OperationAddress[]
     */
    private function findMatchingOperations(ServerRequestInterface $request): array
    {
        $pathFinder = new PathFinder($this->openApi, (string) $request->getUri(), $request->getMethod());

        return $pathFinder->search();
    }
}
openapi-psr7-validator/src/PSR7/MessageValidator.php000064400000000641147361032640016347 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use Psr\Http\Message\MessageInterface;

interface MessageValidator
{
    /**
     * @throws NoPath
     * @throws ValidationFailed
     */
    public function validate(OperationAddress $addr, MessageInterface $message): void;
}
openapi-psr7-validator/src/PSR7/ResponseValidator.php000064400000002363147361032640016564 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\Validators\BodyValidator\BodyValidator;
use League\OpenAPIValidation\PSR7\Validators\HeadersValidator;
use League\OpenAPIValidation\PSR7\Validators\ValidatorChain;
use Psr\Http\Message\ResponseInterface;

class ResponseValidator implements ReusableSchema
{
    /** @var OpenApi */
    protected $openApi;
    /** @var MessageValidator */
    protected $validator;

    public function __construct(OpenApi $schema)
    {
        $this->openApi   = $schema;
        $finder          = new SpecFinder($this->openApi);
        $this->validator = new ValidatorChain(
            new HeadersValidator($finder),
            new BodyValidator($finder)
        );
    }

    public function getSchema(): OpenApi
    {
        return $this->openApi;
    }

    /**
     * @throws ValidationFailed
     */
    public function validate(OperationAddress $opAddr, ResponseInterface $response): void
    {
        $this->validator->validate(
            new ResponseAddress($opAddr->path(), $opAddr->method(), $response->getStatusCode()),
            $response
        );
    }
}
openapi-psr7-validator/src/PSR7/SchemaFactory.php000064400000000264147361032640015646 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;

interface SchemaFactory
{
    public function createSchema(): OpenApi;
}
openapi-psr7-validator/src/PSR7/CallbackResponseValidator.php000064400000002612147361032640020176 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\Validators\BodyValidator\BodyValidator;
use League\OpenAPIValidation\PSR7\Validators\HeadersValidator;
use League\OpenAPIValidation\PSR7\Validators\ValidatorChain;
use Psr\Http\Message\ResponseInterface;

final class CallbackResponseValidator implements ReusableSchema
{
    /** @var OpenApi */
    protected $openApi;
    /** @var MessageValidator */
    protected $validator;

    public function __construct(OpenApi $schema)
    {
        $this->openApi   = $schema;
        $finder          = new SpecFinder($this->openApi);
        $this->validator = new ValidatorChain(
            new HeadersValidator($finder),
            new BodyValidator($finder)
        );
    }

    public function getSchema(): OpenApi
    {
        return $this->openApi;
    }

    /**
     * @throws ValidationFailed
     */
    public function validate(CallbackAddress $opAddr, ResponseInterface $serverRequest): void
    {
        $opAddr = new CallbackResponseAddress(
            $opAddr->path(),
            $opAddr->method(),
            $opAddr->callbackName(),
            $opAddr->callbackMethod(),
            $serverRequest->getStatusCode()
        );
        $this->validator->validate($opAddr, $serverRequest);
    }
}
openapi-psr7-validator/src/PSR7/CacheableSchemaFactory.php000064400000000261147361032640017413 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

interface CacheableSchemaFactory extends SchemaFactory
{
    public function getCacheKey(): string;
}
openapi-psr7-validator/src/PSR7/CallbackResponseAddress.php000064400000001364147361032640017641 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use function sprintf;

class CallbackResponseAddress extends CallbackAddress
{
    /** @var int */
    protected $responseCode;

    public function __construct(string $path, string $method, string $callbackName, string $callbackMethod, int $responseCode)
    {
        parent::__construct($path, $method, $callbackName, $callbackMethod);
        $this->responseCode = $responseCode;
    }

    public function responseCode(): int
    {
        return $this->responseCode;
    }

    public function __toString(): string
    {
        return sprintf('Callback [%s %s %s %s %d]', $this->method, $this->path, $this->callbackName, $this->callbackMethod, $this->responseCode);
    }
}
openapi-psr7-validator/src/PSR7/ResponseAddress.php000064400000001155147361032640016222 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use function sprintf;

class ResponseAddress extends OperationAddress
{
    /** @var int */
    protected $responseCode;

    public function __construct(string $path, string $method, int $responseCode)
    {
        parent::__construct($path, $method);
        $this->responseCode = $responseCode;
    }

    public function responseCode(): int
    {
        return $this->responseCode;
    }

    public function __toString(): string
    {
        return sprintf('Response [%s %s %d]', $this->method, $this->path, $this->responseCode);
    }
}
openapi-psr7-validator/src/PSR7/SpecFinder.php000064400000022236147361032640015143 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\exceptions\TypeErrorException;
use cebe\openapi\spec\Callback;
use cebe\openapi\spec\Header as HeaderSpec;
use cebe\openapi\spec\MediaType;
use cebe\openapi\spec\OpenApi;
use cebe\openapi\spec\Operation;
use cebe\openapi\spec\Parameter;
use cebe\openapi\spec\PathItem;
use cebe\openapi\spec\Reference;
use cebe\openapi\spec\Response as ResponseSpec;
use cebe\openapi\spec\SecurityRequirement;
use cebe\openapi\spec\SecurityScheme;
use League\OpenAPIValidation\PSR7\Exception\NoCallback;
use League\OpenAPIValidation\PSR7\Exception\NoOperation;
use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\NoResponseCode;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use Webmozart\Assert\Assert;

use function json_decode;
use function json_encode;
use function property_exists;

final class SpecFinder
{
    /** @var OpenApi */
    private $openApi;

    public function __construct(OpenApi $openApi)
    {
        $this->openApi = $openApi;
    }

    /**
     * @return Parameter[]
     *
     * @throws NoPath
     */
    public function findOperationAndPathLevelSpecs(OperationAddress $addr): array
    {
        $spec = $this->findOperationSpec($addr);

        // 1. Collect operation-level params
        $pathSpecs = [];

        foreach ($spec->parameters as $p) {
            if ($p->in !== 'path') {
                continue;
            }

            $pathSpecs[$p->name] = $p;
        }

        // 2. Collect path-level params
        $pathSpec = $this->findPathSpec($addr);
        foreach ($pathSpec->parameters as $p) {
            if ($p->in !== 'path') {
                continue;
            }

            $pathSpecs += [$p->name => $p]; // union won't override
        }

        return $pathSpecs;
    }

    /**
     * Find a particular operation (path + method) in the spec
     *
     * @throws NoPath
     */
    public function findOperationSpec(OperationAddress $addr): Operation
    {
        $pathSpec = $this->findPathSpec($addr);

        if (! isset($pathSpec->getOperations()[$addr->method()])) {
            throw NoOperation::fromPathAndMethod($addr->path(), $addr->method());
        }

        $operation = $pathSpec->getOperations()[$addr->method()];
        if ($addr instanceof CallbackAddress) {
            return $this->findCallbackInOperation($addr, $operation);
        }

        return $operation;
    }

    /**
     * Find a particular path in the spec
     *
     * @throws NoPath
     */
    public function findPathSpec(OperationAddress $addr): PathItem
    {
        $finder    = new PathFinder($this->openApi, $addr->path(), $addr->method());
        $pathSpecs = $finder->getPathMatches();

        if (empty($pathSpecs) === true) {
            throw NoPath::fromPath($addr->path());
        }

        return $pathSpecs[0];
    }

    /**
     * @return Parameter[]
     *
     * @throws NoPath
     */
    public function findQuerySpecs(OperationAddress $addr): array
    {
        $spec = $this->findOperationSpec($addr);

        // 1. Collect operation-level params
        $querySpecs = [];

        foreach ($spec->parameters as $p) {
            if ($p->in !== 'query') {
                continue;
            }

            $querySpecs[$p->name] = $p;
        }

        // 2. Collect path-level params
        $pathSpec = $this->findPathSpec($addr);
        foreach ($pathSpec->parameters as $p) {
            if ($p->in !== 'query') {
                continue;
            }

            $querySpecs += [$p->name => $p]; // union won't override
        }

        return $querySpecs;
    }

    /**
     * @return SecurityRequirement[]
     *
     * @throws NoPath
     */
    public function findSecuritySpecs(OperationAddress $addr): array
    {
        $opSpec = $this->findOperationSpec($addr);

        // 1. Collect security params
        if (property_exists($opSpec->getSerializableData(), 'security')) {
            // security is set on operation level
            $securitySpecs = $opSpec->security;
        } else {
            // security is set on root level (fallback option)
            $securitySpecs = $this->openApi->security;
        }

        return $securitySpecs;
    }

    /**
     * @return SecurityScheme[]
     */
    public function findSecuritySchemesSpecs(): array
    {
        return $this->openApi->components ? $this->openApi->components->securitySchemes : [];
    }

    /**
     * @return MediaType[]|Reference[]
     *
     * @throws NoPath
     */
    public function findBodySpec(OperationAddress $addr): array
    {
        if ($addr instanceof ResponseAddress || $addr instanceof CallbackResponseAddress) {
            return $this->findResponseSpec($addr)->content;
        }

        $requestBody = $this->findOperationSpec($addr)->requestBody;

        if (! $requestBody) {
            return [];
        }

        return $requestBody->content;
    }

    /**
     * Find the schema which describes a given response
     *
     * @param ResponseAddress|CallbackResponseAddress $addr
     *
     * @throws NoPath
     */
    public function findResponseSpec($addr): ResponseSpec
    {
        Assert::isInstanceOfAny(
            $addr,
            [
                ResponseAddress::class,
                CallbackResponseAddress::class,
            ]
        );

        $operation = $this->findOperationSpec($addr);

        $response = $operation->responses->getResponse((string) $addr->responseCode());

        if (! $response) {
            $response = $operation->responses->getResponse('default');
        }

        if (! $response) {
            throw NoResponseCode::fromPathAndMethodAndResponseCode(
                $addr->path(),
                $addr->method(),
                $addr->responseCode()
            );
        }

        return $response;
    }

    /**
     * @return array<string, HeaderSpec|Parameter>
     *
     * @throws NoPath
     */
    public function findHeaderSpecs(OperationAddress $addr): array
    {
        // Response headers are specified differently from request headers
        if ($addr instanceof ResponseAddress || $addr instanceof CallbackResponseAddress) {
            return $this->findResponseSpec($addr)->headers;
        }

        $spec = $this->findOperationSpec($addr);

        // 1. Collect operation level headers from "parameters" keyword
        // An API call may require that custom headers be sent with an HTTP request. OpenAPI lets you define custom
        // request headers as in: header parameters.
        /** @var array<string, HeaderSpec|Parameter> $headerSpecs */
        $headerSpecs = [];

        foreach ($spec->parameters as $p) {
            if ($p->in !== 'header') {
                continue;
            }

            $headerData = json_decode(json_encode($p->getSerializableData()), true);
            unset($headerData['in'], $headerData['name']);
            try {
                $headerSpecs[$p->name] = new HeaderSpec($headerData);
            } catch (TypeErrorException $e) {
                throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
            }
        }

        // 2. Collect path-level headers from "parameters" keyword
        // Path level params are fall-backs
        $pathSpec = $this->findPathSpec($addr);

        foreach ($pathSpec->parameters as $p) {
            if ($p->in !== 'header') {
                continue;
            }

            $headerSpecs += [$p->name => $p]; // union won't override
        }

        return $headerSpecs;
    }

    /**
     * @return Parameter[]
     *
     * @throws NoPath
     */
    public function findCookieSpecs(OperationAddress $addr): array
    {
        $spec = $this->findOperationSpec($addr);

        $cookieSpecs = [];

        // 1. Find operation level params
        foreach ($spec->parameters as $p) {
            if ($p->in !== 'cookie') {
                continue;
            }

            $cookieSpecs[$p->name] = $p;
        }

        // 2. Collect path-level params
        $pathSpec = $this->findPathSpec($addr);
        foreach ($pathSpec->parameters as $p) {
            if ($p->in !== 'cookie') {
                continue;
            }

            $cookieSpecs += [$p->name => $p]; // union won't override
        }

        return $cookieSpecs;
    }

    /**
     * @throws NoCallback
     */
    private function findCallbackInOperation(CallbackAddress $addr, Operation $operation): Operation
    {
        $callbacks = $operation->callbacks;
        if (! isset($callbacks[$addr->callbackName()])) {
            throw NoCallback::fromCallbackPath(
                $addr->path(),
                $addr->method(),
                $addr->callbackName(),
                $addr->callbackMethod()
            );
        }

        /** @var Callback $callback */
        $callback = $callbacks[$addr->callbackName()];
        if (! isset($callback->getRequest()->getOperations()[$addr->callbackMethod()])) {
            throw NoCallback::fromCallbackPath(
                $addr->path(),
                $addr->method(),
                $addr->callbackName(),
                $addr->callbackMethod()
            );
        }

        return $callback->getRequest()->getOperations()[$addr->callbackMethod()];
    }
}
openapi-psr7-validator/src/PSR7/CallbackRequestValidator.php000064400000002614147361032640020032 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\Validators\BodyValidator\BodyValidator;
use League\OpenAPIValidation\PSR7\Validators\CookiesValidator\CookiesValidator;
use League\OpenAPIValidation\PSR7\Validators\HeadersValidator;
use League\OpenAPIValidation\PSR7\Validators\QueryArgumentsValidator;
use League\OpenAPIValidation\PSR7\Validators\ValidatorChain;
use Psr\Http\Message\RequestInterface;

final class CallbackRequestValidator implements ReusableSchema
{
    /** @var OpenApi */
    protected $openApi;
    /** @var MessageValidator */
    protected $validator;

    public function __construct(OpenApi $schema)
    {
        $this->openApi   = $schema;
        $finder          = new SpecFinder($this->openApi);
        $this->validator = new ValidatorChain(
            new HeadersValidator($finder),
            new CookiesValidator($finder),
            new BodyValidator($finder),
            new QueryArgumentsValidator($finder)
        );
    }

    public function getSchema(): OpenApi
    {
        return $this->openApi;
    }

    /**
     * @throws ValidationFailed
     */
    public function validate(CallbackAddress $opAddr, RequestInterface $serverRequest): void
    {
        $this->validator->validate($opAddr, $serverRequest);
    }
}
openapi-psr7-validator/src/PSR7/Validators/ArrayValidator.php000064400000003565147361032640020161 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use cebe\openapi\spec\Parameter;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidParameter;
use League\OpenAPIValidation\PSR7\Exception\Validation\RequiredParameterMissing;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;

use function array_key_exists;

/**
 * Validates given array against given specs
 */
class ArrayValidator
{
    /** @var Parameter[] */
    private $specs;

    /**
     * @param Parameter[] $specs
     */
    public function __construct(array $specs)
    {
        $this->specs = $specs;
    }

    /**
     * @param mixed[] $params
     *
     * @throws InvalidParameter
     * @throws RequiredParameterMissing
     */
    public function validateArray(array $params, int $validationStrategy): void
    {
        foreach ($this->specs as $name => $spec) {
            if ($spec->required && ! array_key_exists($name, $params)) {
                throw RequiredParameterMissing::fromName($name);
            }
        }

        // Note: By default, OpenAPI treats all request parameters as optional.
        $validator = new SchemaValidator($validationStrategy);

        foreach ($params as $name => $argumentValue) {
            // skip if there is no spec for this argument
            if (! array_key_exists($name, $this->specs)) {
                continue;
            }

            $parameter = SerializedParameter::fromSpec($this->specs[$name]);
            try {
                $validator->validate($parameter->deserialize($argumentValue), $parameter->getSchema(), new BreadCrumb($name));
            } catch (SchemaMismatch $e) {
                throw InvalidParameter::becauseValueDidNotMatchSchema($name, $argumentValue, $e);
            }
        }
    }
}
openapi-psr7-validator/src/PSR7/Validators/QueryArgumentsValidator.php000064400000005033147361032640022066 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidParameter;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidQueryArgs;
use League\OpenAPIValidation\PSR7\Exception\Validation\RequiredParameterMissing;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\SpecFinder;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;

use function parse_str;

/**
 * @see https://swagger.io/docs/specification/describing-parameters/
 */
final class QueryArgumentsValidator implements MessageValidator
{
    use ValidationStrategy;

    /** @var SpecFinder */
    private $finder;

    public function __construct(SpecFinder $finder)
    {
        $this->finder = $finder;
    }

    /** {@inheritdoc} */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        if (! $message instanceof RequestInterface) {
            return;
        }

        $validationStrategy   = $this->detectValidationStrategy($message);
        $parsedQueryArguments = $this->parseQueryArguments($message);
        $this->validateQueryArguments($addr, $parsedQueryArguments, $validationStrategy);
    }

    /**
     * @param mixed[] $parsedQueryArguments [limit=>10]
     *
     * @throws InvalidQueryArgs
     * @throws NoPath
     */
    private function validateQueryArguments(OperationAddress $addr, array $parsedQueryArguments, int $validationStrategy): void
    {
        $validator = new ArrayValidator($this->finder->findQuerySpecs($addr));

        try {
            $validator->validateArray($parsedQueryArguments, $validationStrategy);
        } catch (RequiredParameterMissing $e) {
            throw InvalidQueryArgs::becauseOfMissingRequiredArgument($e->name(), $addr, $e);
        } catch (InvalidParameter $e) {
            throw InvalidQueryArgs::becauseValueDoesNotMatchSchema($e->name(), $e->value(), $addr, $e);
        }
    }

    /**
     * @return mixed[] like [offset => 10]
     */
    private function parseQueryArguments(RequestInterface $message): array
    {
        if ($message instanceof ServerRequestInterface) {
            $parsedQueryArguments = $message->getQueryParams();
        } else {
            parse_str($message->getUri()->getQuery(), $parsedQueryArguments);
        }

        return $parsedQueryArguments;
    }
}
openapi-psr7-validator/src/PSR7/Validators/ValidationStrategy.php000064400000001201147361032640021033 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use League\OpenAPIValidation\Schema\SchemaValidator;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseInterface;

trait ValidationStrategy
{
    /**
     * Distinguish requests and responses, so we can treat them differently (writeOnly/readOnly OAS keywords)
     */
    protected function detectValidationStrategy(MessageInterface $message): int
    {
        if ($message instanceof ResponseInterface) {
            return SchemaValidator::VALIDATE_AS_RESPONSE;
        }

        return SchemaValidator::VALIDATE_AS_REQUEST;
    }
}
openapi-psr7-validator/src/PSR7/Validators/BodyValidator/BodyDeserialization.php000064400000001714147361032640023736 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\BodyValidator;

use cebe\openapi\spec\Schema;
use cebe\openapi\spec\Type as CebeType;
use League\OpenAPIValidation\PSR7\Validators\SerializedParameter;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;

trait BodyDeserialization
{
    /**
     * @param array<string, mixed> $body
     *
     * @return array<string, mixed> $body
     *
     * @throws SchemaMismatch
     */
    protected function deserializeBody(array $body, Schema $schema): array
    {
        if ($schema->type !== CebeType::OBJECT) {
            return $body;
        }

        foreach ($schema->properties as $propName => $propSchema) {
            if (! isset($body[$propName])) {
                continue;
            }

            $param           = new SerializedParameter($propSchema);
            $body[$propName] = $param->deserialize($body[$propName]);
        }

        return $body;
    }
}
openapi-psr7-validator/src/PSR7/Validators/BodyValidator/MultipartValidator.php000064400000027053147361032640023625 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\BodyValidator;

use cebe\openapi\spec\Encoding;
use cebe\openapi\spec\Header;
use cebe\openapi\spec\MediaType;
use cebe\openapi\spec\Schema;
use cebe\openapi\spec\Type as CebeType;
use InvalidArgumentException;
use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\Validators\SerializedParameter;
use League\OpenAPIValidation\PSR7\Validators\ValidationStrategy;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\Exception\TypeMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use Riverline\MultiPartParser\Converters\PSR7;
use Riverline\MultiPartParser\StreamedPart;
use RuntimeException;

use function array_replace;
use function in_array;
use function is_array;
use function json_decode;
use function json_last_error;
use function json_last_error_msg;
use function preg_match;
use function sprintf;
use function str_replace;
use function strpos;

use const JSON_ERROR_NONE;

/**
 * Should validate multipart/* body types
 */
class MultipartValidator implements MessageValidator
{
    use ValidationStrategy;
    use BodyDeserialization;

    private const HEADER_CONTENT_TYPE = 'Content-Type';

    /** @var MediaType */
    protected $mediaTypeSpec;
    /** @var string */
    protected $contentType;

    public function __construct(MediaType $mediaTypeSpec, string $contentType)
    {
        $this->mediaTypeSpec = $mediaTypeSpec;
        $this->contentType   = $contentType;
    }

    /**
     * @throws NoPath
     * @throws ValidationFailed
     */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        /** @var Schema $schema */
        $schema = $this->mediaTypeSpec->schema;

        // 0. Multipart body message MUST be described with a set of object properties
        if ($schema->type !== CebeType::OBJECT) {
            throw TypeMismatch::becauseTypeDoesNotMatch('object', $schema->type);
        }

        if ($message->getBody()->getSize()) {
            $this->validatePlainBodyMultipart($addr, $message, $schema);
        } elseif ($message instanceof ServerRequestInterface) {
            $this->validateServerRequestMultipart($addr, $message, $schema);
        }
    }

    private function validatePlainBodyMultipart(
        OperationAddress $addr,
        MessageInterface $message,
        Schema $schema
    ): void {
        // 1. Parse message body
        $document = PSR7::convert($message);

        $validator = new SchemaValidator($this->detectValidationStrategy($message));
        try {
            $body = $this->deserializeBody($this->parseMultipartData($addr, $document), $schema);
            $validator->validate($body, $schema);
        } catch (SchemaMismatch $e) {
            throw InvalidBody::becauseBodyDoesNotMatchSchema($this->contentType, $addr, $e);
        }

        // 2. Validate specified part encodings and headers
        // @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encoding-object
        // The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
        // An encoding attribute is introduced to give you control over the serialization of parts of multipart request bodies.
        // This attribute is only applicable to "multipart" and "application/x-www-form-urlencoded" request bodies.
        $encodings = $this->mediaTypeSpec->encoding;

        foreach ($encodings as $partName => $encoding) {
            $parts = $document->getPartsByName($partName); // multiple parts share a name?
            if (! $parts) {
                throw new RuntimeException(sprintf(
                    'Specified body part %s is not found',
                    $partName
                ));
            }

            foreach ($parts as $part) {
                // 2.1 parts encoding
                $partContentType     = $part->getHeader(self::HEADER_CONTENT_TYPE);
                $encodingContentType = $this->detectEncondingContentType($encoding, $part, $schema->properties[$partName]);
                if (strpos($encodingContentType, '*') === false) {
                    // strict comparison (ie "image/jpeg")
                    if ($encodingContentType !== $partContentType) {
                        throw InvalidBody::becauseBodyDoesNotMatchSchemaMultipart(
                            $partName,
                            $partContentType,
                            $addr
                        );
                    }
                } else {
                    // loose comparison (ie "image/*")
                    $encodingContentType = str_replace('*', '.*', $encodingContentType);
                    if (! preg_match('#' . $encodingContentType . '#', $partContentType)) {
                        throw InvalidBody::becauseBodyDoesNotMatchSchemaMultipart(
                            $partName,
                            $partContentType,
                            $addr
                        );
                    }
                }

                // 2.2. parts headers
                $validator = new SchemaValidator($this->detectValidationStrategy($message));
                foreach ($encoding->headers as $headerName => $headerSpec) {
                    /** @var Header $headerSpec */
                    $headerSchema = $headerSpec->schema;
                    $headerValue  = $part->getHeader($headerName);

                    if ($headerValue === null) {
                        throw InvalidHeaders::becauseOfMissingRequiredHeaderMupripart($partName, $headerName, $addr);
                    }

                    $header = SerializedParameter::fromSpec($headerSpec);
                    try {
                        $validator->validate($header->deserialize($headerValue), $headerSchema);
                    } catch (SchemaMismatch $e) {
                        throw InvalidHeaders::becauseValueDoesNotMatchSchemaMultipart($partName, $headerName, $headerValue, $addr, $e);
                    }
                }
            }
        }
    }

    /**
     * Prepare a Multipart message body (a set of normal parts) for validation against a schema
     *
     * @see https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
     * @see https://swagger.io/docs/specification/describing-request-body/multipart-requests/
     *
     * @return mixed[]
     *
     * @throws InvalidBody
     * @throws TypeMismatch
     */
    private function parseMultipartData(OperationAddress $addr, StreamedPart $document): array
    {
        $multipartData = []; // a buffer to fill up with message parts
        foreach ($document->getParts() as $i => $part) {
            $partContentType = $part->getHeader('Content-Type');

            if (! empty($partContentType) && preg_match('#^application/.*json$#', $partContentType)) {
                $partBody = json_decode($part->getBody(), true);
                if (json_last_error() !== JSON_ERROR_NONE) {
                    throw InvalidBody::becauseBodyIsNotValidJson(json_last_error_msg(), $addr);
                }
            } else {
                $partBody = $part->getBody();
            }

            // if name is not set, it should be validated with "additionalProperties" keyword
            $multipartData[$part->getName() ?? '____' . $i] = $partBody;
        }

        return $multipartData;
    }

    private function detectEncondingContentType(Encoding $encoding, StreamedPart $part, Schema $partSchema): string
    {
        $contentType = $encoding->contentType;

        if (! $contentType) {
            // fallback strategy:
            // @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encoding-object
            // @see https://swagger.io/docs/specification/describing-request-body/multipart-requests/
            //
            // Default value depends on the property type: for string with format being binary – application/octet-stream;
            // for other primitive types – text/plain; for object - application/json; for array – the default is defined based on the inner type.
            // The value can be a specific media type (e.g. application/json), a wildcard media type (e.g. image/*),
            // or a comma-separated list of the two types.
            if ($partSchema->type === 'string') {
                if (in_array($partSchema->format, ['binary', 'base64'])) {
                    $contentType = 'application/octet-stream';
                } else {
                    $contentType = 'text/plain';
                }
            } elseif (in_array($partSchema->type, ['object', 'array'])) {
                $contentType = 'application/json';
            }
        }

        return $contentType;
    }

    /**
     * ServerRequest does not have a plain HTTP body which we can parse. Instead, it has a parsed values in
     * getParsedBody() (POST data) and getUploadedFiles (FILES data)
     */
    private function validateServerRequestMultipart(
        OperationAddress $addr,
        ServerRequestInterface $message,
        Schema $schema
    ): void {
        $body = (array) $message->getParsedBody();

        $files = $this->normalizeFiles($message->getUploadedFiles());

        $body = array_replace($body, $files);

        $validator = new SchemaValidator($this->detectValidationStrategy($message));
        try {
            $validator->validate($body, $schema);
        } catch (SchemaMismatch $e) {
            throw InvalidBody::becauseBodyDoesNotMatchSchema($this->contentType, $addr, $e);
        }

        // 2. Validate specified part encodings and headers
        // @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encoding-object
        // The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
        // An encoding attribute is introduced to give you control over the serialization of parts of multipart request bodies.
        // This attribute is only applicable to "multipart" and "application/x-www-form-urlencoded" request bodies.
        $encodings = $this->mediaTypeSpec->encoding;

        foreach ($encodings as $partName => $encoding) {
            if (! isset($body[$partName])) {
                throw new RuntimeException(sprintf('Specified body part %s is not found', $partName));
            }

            $part = $body[$partName];

            // 2.1 parts encoding
            // ...values are parsed already by php core...

            // 2.2. parts headers
            // ...headers are parsed already by webserver...
        }
    }

    /**
     * @param UploadedFileInterface[]|array[] $files
     *
     * @return mixed[]
     */
    private function normalizeFiles(array $files): array
    {
        $normalized = [];

        foreach ($files as $name => $file) {
            if ($file instanceof UploadedFileInterface) {
                $normalized[$name] = '~~~binary~~~';
            } elseif (is_array($file)) {
                $normalized[$name] = $this->normalizeFiles($file);
            } else {
                throw new InvalidArgumentException('Invalid file tree in request');
            }
        }

        return $normalized;
    }
}
openapi-psr7-validator/src/PSR7/Validators/BodyValidator/UnipartValidator.php000064400000004002147361032640023253 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\BodyValidator;

use cebe\openapi\spec\MediaType;
use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\Validators\ValidationStrategy;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Psr\Http\Message\MessageInterface;

use function json_decode;
use function json_last_error;
use function json_last_error_msg;
use function preg_match;

use const JSON_ERROR_NONE;

class UnipartValidator implements MessageValidator
{
    use ValidationStrategy;

    /** @var MediaType */
    protected $mediaTypeSpec;
    /** @var string */
    protected $contentType;

    public function __construct(MediaType $mediaTypeSpec, string $contentType)
    {
        $this->mediaTypeSpec = $mediaTypeSpec;
        $this->contentType   = $contentType;
    }

    /**
     * @throws NoPath
     * @throws ValidationFailed
     */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        if (preg_match('#^application/.*json$#', $this->contentType)) {
            $body = json_decode((string) $message->getBody(), true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw InvalidBody::becauseBodyIsNotValidJson(json_last_error_msg(), $addr);
            }
        } else {
            $body = (string) $message->getBody();
        }

        $validator = new SchemaValidator($this->detectValidationStrategy($message));
        $schema    = $this->mediaTypeSpec->schema;
        try {
            $validator->validate($body, $schema);
        } catch (SchemaMismatch $e) {
            throw InvalidBody::becauseBodyDoesNotMatchSchema($this->contentType, $addr, $e);
        }
    }
}
openapi-psr7-validator/src/PSR7/Validators/BodyValidator/FormUrlencodedValidator.php000064400000006131147361032640024546 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\BodyValidator;

use cebe\openapi\spec\MediaType;
use cebe\openapi\spec\Schema;
use cebe\openapi\spec\Type as CebeType;
use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\Validators\ValidationStrategy;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\Exception\TypeMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Psr\Http\Message\MessageInterface;

use function parse_str;

/**
 * Should validate "application/x-www-form-urlencoded" body types
 */
class FormUrlencodedValidator implements MessageValidator
{
    use ValidationStrategy;
    use BodyDeserialization;

    /** @var MediaType */
    protected $mediaTypeSpec;
    /** @var string */
    protected $contentType;

    public function __construct(MediaType $mediaTypeSpec, string $contentType)
    {
        $this->mediaTypeSpec = $mediaTypeSpec;
        $this->contentType   = $contentType;
    }

    /**
     * @throws NoPath
     * @throws ValidationFailed
     */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        /** @var Schema $schema */
        $schema = $this->mediaTypeSpec->schema;

        // 0. Multipart body message MUST be described with a set of object properties
        if ($schema->type !== CebeType::OBJECT) {
            throw TypeMismatch::becauseTypeDoesNotMatch('object', $schema->type);
        }

        // 1. Parse message body

        $validator = new SchemaValidator($this->detectValidationStrategy($message));
        try {
            $body = $this->deserializeBody($this->parseUrlencodedData($message), $schema);
            $validator->validate($body, $schema);
        } catch (SchemaMismatch $e) {
            throw InvalidBody::becauseBodyDoesNotMatchSchema($this->contentType, $addr, $e);
        }

        // 3. Validate specified part encodings and headers
        // @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encoding-object
        // The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
        // An encoding attribute is introduced to give you control over the serialization of parts of multipart request bodies.
        // This attribute is only applicable to "multipart" and "application/x-www-form-urlencoded" request bodies.
        $encodings = $this->mediaTypeSpec->encoding;

        // todo URL Serialization:
        // @see https://github.com/lezhnev74/openapi-psr7-validator/issues/47
    }

    /**
     * @return mixed[]
     */
    protected function parseUrlencodedData(MessageInterface $message): array
    {
        $body = [];

        parse_str(
            (string) $message->getBody(),
            $body
        );

        return $body;
    }
}
openapi-psr7-validator/src/PSR7/Validators/BodyValidator/BodyValidator.php000064400000010335147361032640022534 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\BodyValidator;

use cebe\openapi\spec\MediaType;
use cebe\openapi\spec\Reference;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\SpecFinder;
use League\OpenAPIValidation\PSR7\Validators\ValidationStrategy;
use Psr\Http\Message\MessageInterface;

use function explode;
use function preg_match;
use function strtok;

/**
 * Supports validation for different media types of bodies,
 * including JSON and multipart types
 */
final class BodyValidator implements MessageValidator
{
    private const HEADER_CONTENT_TYPE = 'Content-Type';
    use ValidationStrategy;

    /** @var SpecFinder */
    private $finder;

    public function __construct(SpecFinder $finder)
    {
        $this->finder = $finder;
    }

    /** {@inheritdoc} */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        $mediaTypeSpecs = $this->finder->findBodySpec($addr);

        if (empty($mediaTypeSpecs)) {
            // edge case: if "content" keyword is not set (body can be anything as no expectations set)
            return;
        }

        // Detect ContentType of the message
        $contentType = $this->messageContentType($message);
        if (! $contentType) {
            throw InvalidHeaders::becauseOfMissingRequiredHeader(self::HEADER_CONTENT_TYPE, $addr);
        }

        // does the response contain one of described media types?
        $mediaTypeSpec = $this->matchMediaTypeSpec($mediaTypeSpecs, $contentType);
        if ($mediaTypeSpec === null) {
            throw InvalidHeaders::becauseContentTypeIsNotExpected($contentType, $addr);
        }

        // detect the schema for the media type
        $schema = $mediaTypeSpec->schema;
        if (! $schema) {
            // no schema means no validation
            // note: schema is REQUIRED to define the input parameters to the operation when using multipart content
            return;
        }

        // Validate message body
        if (preg_match('#^multipart/.*#', $contentType)) {
            (new MultipartValidator($mediaTypeSpec, $contentType))->validate($addr, $message);
        } elseif (preg_match('#^application/x-www-form-urlencoded$#', $contentType)) {
            (new FormUrlencodedValidator($mediaTypeSpec, $contentType))->validate($addr, $message);
        } else {
            (new UnipartValidator($mediaTypeSpec, $contentType))->validate($addr, $message);
        }
    }

    private function messageContentType(MessageInterface $message): ?string
    {
        $contentTypes = $message->getHeader(self::HEADER_CONTENT_TYPE);
        if (! $contentTypes) {
            return null;
        }

        $contentType = $contentTypes[0]; // use the first value

        // As per https://tools.ietf.org/html/rfc7231#section-3.1.1.5 and https://tools.ietf.org/html/rfc7231#section-3.1.1.1
        // ContentType can contain multiple statements (type/subtype + parameters), ie: 'multipart/form-data; charset=utf-8; boundary=__X_PAW_BOUNDARY__'
        // OpenAPI Spec only defines the first part of the header value (type/subtype)
        // Other parameters SHOULD be skipped
        $contentType = (string) strtok($contentType, ';');

        return $contentType;
    }

    /**
     * Match the spec from media type specs for the given media type.
     *
     * @param Reference[]|MediaType[] $mediaTypeSpecs
     *
     * @return Reference|MediaType|null
     */
    private function matchMediaTypeSpec(array $mediaTypeSpecs, string $mediaType)
    {
        [$mediaTypeType, $mediaTypeSubType] = explode('/', $mediaType);

        // Allow sub-type ranges and match all, like 'image/*', '*/*'
        // In the order: type/subtype > type/* > */*
        // see: https://tools.ietf.org/html/rfc7231#section-5.3.2
        $candidateContentTypes = [
            $mediaType,
            $mediaTypeType . '/*',
            '*/*',
        ];

        foreach ($candidateContentTypes as $type) {
            if (isset($mediaTypeSpecs[$type])) {
                return $mediaTypeSpecs[$type];
            }
        }

        return null;
    }
}
openapi-psr7-validator/src/PSR7/Validators/PathValidator.php000064400000003733147361032640017774 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidParameter;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidPath;
use League\OpenAPIValidation\PSR7\Exception\Validation\RequiredParameterMissing;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\SpecFinder;
use LogicException;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;

final class PathValidator implements MessageValidator
{
    use ValidationStrategy;

    /** @var SpecFinder */
    private $finder;

    public function __construct(SpecFinder $finder)
    {
        $this->finder = $finder;
    }

    /** {@inheritdoc} */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        if (! ($message instanceof RequestInterface)) {
            return;
        }

        $this->validateRequest($addr, $message);
    }

    /**
     * @throws InvalidPath
     * @throws NoPath
     */
    private function validateRequest(OperationAddress $addr, RequestInterface $message): void
    {
        $validator        = new ArrayValidator($this->finder->findOperationAndPathLevelSpecs($addr));
        $path             = $message->getUri()->getPath();
        $pathParsedParams = $addr->parseParams($path); // ['id'=>12]

        try {
            $validator->validateArray($pathParsedParams, $this->detectValidationStrategy($message));
        } catch (InvalidParameter $e) {
            throw InvalidPath::becauseValueDoesNotMatchSchema($e->name(), $e->value(), $addr, $e);
        } catch (RequiredParameterMissing $e) {
            throw new LogicException('RequiredParameterMissing should not be thrown in PathValidator, ' .
                'because presence of all parameters have to be checked before', 0, $e);
        }
    }
}
openapi-psr7-validator/src/PSR7/Validators/SerializedParameter.php000064400000015532147361032640021166 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use cebe\openapi\spec\Parameter as CebeParameter;
use cebe\openapi\spec\Schema as CebeSchema;
use cebe\openapi\spec\Type as CebeType;
use League\OpenAPIValidation\Schema\Exception\ContentTypeMismatch;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\Exception\TypeMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function explode;
use function in_array;
use function is_array;
use function is_float;
use function is_int;
use function is_numeric;
use function is_scalar;
use function is_string;
use function json_decode;
use function json_last_error;
use function key;
use function preg_match;
use function reset;
use function strtolower;

use const JSON_ERROR_NONE;

final class SerializedParameter
{
    private const STYLE_FORM            = 'form';
    private const STYLE_SPACE_DELIMITED = 'spaceDelimited';
    private const STYLE_PIPE_DELIMITED  = 'pipeDelimited';
    private const STYLE_DEEP_OBJECT     = 'deepObject';
    private const STYLE_DELIMITER_MAP   = [
        self::STYLE_FORM => ',',
        self::STYLE_SPACE_DELIMITED => ' ',
        self::STYLE_PIPE_DELIMITED => '|',
    ];

    /** @var CebeSchema */
    private $schema;
    /** @var string|null */
    private $contentType;
    /** @var string|null */
    private $style;
    /** @var bool|null */
    private $explode;

    public function __construct(CebeSchema $schema, ?string $contentType = null, ?string $style = null, ?bool $explode = null)
    {
        $this->schema      = $schema;
        $this->contentType = $contentType;
        $this->style       = $style;
        $this->explode     = $explode;
    }

    public static function fromSpec(CebeParameter $parameter): self
    {
        $content = $parameter->content;
        try {
            if ($parameter->schema !== null) {
                Validator::not(Validator::notEmpty())->assert($content);

                return new self($parameter->schema, null, $parameter->style, $parameter->explode);
            }

            Validator::length(1, 1)->assert($content);
        } catch (Exception | ExceptionInterface $e) {
            // If there is a `schema`, `content` must be empty.
            // If there isn't a `schema`, a `content` with exactly 1 property must exist.
            // @see https://swagger.io/docs/specification/describing-parameters/#schema-vs-content
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        $schema      = reset($content)->schema;
        $contentType = key($content);

        return new self($schema, $contentType, $parameter->style, $parameter->explode);
    }

    /**
     * @param mixed $value
     *
     * @return mixed
     *
     * @throws SchemaMismatch
     */
    public function deserialize($value)
    {
        if ($this->isJsonContentType()) {
            // Value MUST be a string.
            if (! is_string($value)) {
                throw TypeMismatch::becauseTypeDoesNotMatch('string', $value);
            }

            $decodedValue = json_decode($value, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw ContentTypeMismatch::fromContentType($this->contentType, $value);
            }

            return $decodedValue;
        }

        $value = $this->castToSchemaType($value, $this->schema->type);

        return $value;
    }

    private function isJsonContentType(): bool
    {
        return $this->contentType !== null && preg_match('#^application/.*json$#', $this->contentType) !== false;
    }

    /**
     * @param mixed $value
     *
     * @return mixed
     */
    private function castToSchemaType($value, ?string $type)
    {
        if (($type === CebeType::BOOLEAN) && is_scalar($value) && preg_match('#^(true|false)$#i', (string) $value)) {
            return is_string($value) ? strtolower($value) === 'true' : (bool) $value;
        }

        if (
            ($type === CebeType::NUMBER)
            && is_scalar($value) && is_numeric($value)
        ) {
            return is_int($value) ? (int) $value : (float) $value;
        }

        if (
            ($type === CebeType::INTEGER)
            && is_scalar($value) && ! is_float($value) && preg_match('#^[-+]?\d+$#', (string) $value)
        ) {
            return (int) $value;
        }

        if (($type === CebeType::ARRAY) && is_string($value)) {
            return $this->convertToSerializationStyle($value, $this->schema);
        }

        if (($type === CebeType::ARRAY) && is_array($value)) {
            return $this->convertToSerializationStyle($value, $this->schema);
        }

        if (($type === CebeType::OBJECT) && is_array($value)) {
            return $this->convertToSerializationStyle($value, $this->schema);
        }

        return $value;
    }

    /**
     * @param mixed           $value
     * @param CebeSchema|null $schema - optional schema of value to convert it in case of DeepObject serialisation
     *
     * @return mixed
     */
    protected function convertToSerializationStyle($value, ?CebeSchema $schema)
    {
        if (in_array($this->style, [self::STYLE_FORM, self::STYLE_SPACE_DELIMITED, self::STYLE_PIPE_DELIMITED], true)) {
            if ($this->explode === false) {
                $value = explode(self::STYLE_DELIMITER_MAP[$this->style], $value);
            }

            foreach ($value as &$val) {
                $val = $this->castToSchemaType($val, $schema->items->type ?? null);
            }

            return $value;
        }

        if ($schema && $this->style === self::STYLE_DEEP_OBJECT) {
            foreach ($value as $key => &$val) {
                $childSchema = $this->getChildSchema($schema, (string) $key);
                if (is_array($val)) {
                    $val = $this->convertToSerializationStyle($val, $childSchema);
                } else {
                    $val = $this->castToSchemaType($val, $childSchema->type ?? null);
                }
            }

            return $value;
        }

        return $value;
    }

    public function getSchema(): CebeSchema
    {
        return $this->schema;
    }

    protected function getChildSchema(CebeSchema $schema, string $key): ?CebeSchema
    {
        if ($schema->type === CebeType::OBJECT) {
            if (($schema->properties[$key] ?? false) && $schema->properties[$key] instanceof CebeSchema) {
                return $schema->properties[$key];
            }

            if ($schema->additionalProperties instanceof CebeSchema) {
                return $schema->additionalProperties;
            }
        }

        if ($schema->type === CebeType::ARRAY && $schema->items instanceof CebeSchema) {
            return $schema->items;
        }

        return null;
    }
}
openapi-psr7-validator/src/PSR7/Validators/HeadersValidator.php000064400000003255147361032640020452 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\SpecFinder;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Psr\Http\Message\MessageInterface;

final class HeadersValidator implements MessageValidator
{
    use ValidationStrategy;

    /** @var SpecFinder */
    private $finder;

    public function __construct(SpecFinder $finder)
    {
        $this->finder = $finder;
    }

    /** {@inheritdoc} */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        $headerSpecs = $this->finder->findHeaderSpecs($addr);

        $validator = new SchemaValidator($this->detectValidationStrategy($message));

        // Check if message misses required headers
        foreach ($headerSpecs as $header => $spec) {
            if ($spec->required && ! $message->hasHeader($header)) {
                throw InvalidHeaders::becauseOfMissingRequiredHeader($header, $addr);
            }

            $parameter = SerializedParameter::fromSpec($spec);

            foreach ($message->getHeader($header) as $headerValue) {
                try {
                    $validator->validate($parameter->deserialize($headerValue), $spec->schema);
                } catch (SchemaMismatch $exception) {
                    throw InvalidHeaders::becauseValueDoesNotMatchSchema($header, $headerValue, $addr, $exception);
                }
            }
        }
    }
}
openapi-psr7-validator/src/PSR7/Validators/CookiesValidator/CookiesValidator.php000064400000002434147361032640023733 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\CookiesValidator;

use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\SpecFinder;
use League\OpenAPIValidation\PSR7\Validators\ValidationStrategy;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;

final class CookiesValidator implements MessageValidator
{
    use ValidationStrategy;

    /** @var SpecFinder */
    private $finder;

    public function __construct(SpecFinder $finder)
    {
        $this->finder = $finder;
    }

    /** {@inheritdoc} */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        $specs = $this->finder->findCookieSpecs($addr);

        // Note that Response cookies (SetCookie headers) are validated as simple headers
        // @see https://github.com/OAI/OpenAPI-Specification/issues/1237
        if ($message instanceof ServerRequestInterface) {
            (new ServerRequestCookieValidator($specs))->validate($addr, $message);
        } elseif ($message instanceof RequestInterface) {
            (new RequestCookieValidator($specs))->validate($addr, $message);
        }
    }
}
openapi-psr7-validator/src/PSR7/Validators/CookiesValidator/RequestCookieValidator.php000064400000006265147361032640025127 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\CookiesValidator;

use cebe\openapi\spec\Parameter;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidCookies;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\Validators\SerializedParameter;
use League\OpenAPIValidation\PSR7\Validators\ValidationStrategy;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function explode;
use function implode;

class RequestCookieValidator implements MessageValidator
{
    use ValidationStrategy;

    /** @var Parameter[] */
    private $specs;

    /**
     * @param Parameter[] $specs
     */
    public function __construct(array $specs)
    {
        $this->specs = $specs;
    }

    /**
     * @throws InvalidCookies
     */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        Assert::isInstanceOf($message, RequestInterface::class);
        $cookies = $this->getCookiesFromMessage($message);
        $this->checkRequiredCookies($cookies, $addr);
        $this->checkCookiesAgainstSchema($message, $addr, $cookies);
    }

    /**
     * @param string[] $cookies
     *
     * @throws InvalidCookies
     */
    private function checkRequiredCookies(array $cookies, OperationAddress $addr): void
    {
        foreach ($this->specs as $cookieName => $spec) {
            if ($spec->required && ! array_key_exists($cookieName, $cookies)) {
                throw InvalidCookies::becauseOfMissingRequiredCookie($cookieName, $addr);
            }
        }
    }

    /**
     * @param string[] $cookies
     *
     * @throws InvalidCookies
     */
    private function checkCookiesAgainstSchema(RequestInterface $request, OperationAddress $addr, array $cookies): void
    {
        $validator = new SchemaValidator($this->detectValidationStrategy($request));

        foreach ($cookies as $cookieName => $cookie) {
            if (! isset($this->specs[$cookieName])) {
                continue;
            }

            $parameter = SerializedParameter::fromSpec($this->specs[$cookieName]);
            try {
                $validator->validate($parameter->deserialize($cookie), $parameter->getSchema());
            } catch (SchemaMismatch $e) {
                throw InvalidCookies::becauseValueDoesNotMatchSchema($cookieName, $cookie, $addr, $e);
            }
        }
    }

    /**
     * @return string[]
     */
    private function getCookiesFromMessage(MessageInterface $message): array
    {
        // Needed in case it is an array of cookies.
        $cookieString  = implode('; ', $message->getHeader('Cookie'));
        $headerCookies = explode('; ', $cookieString);

        $cookies = [];
        foreach ($headerCookies as $itm) {
            $pairParts              = explode('=', $itm, 2);
            $pairParts[1]           = $pairParts[1] ?? '';
            $cookies[$pairParts[0]] = $pairParts[1];
        }

        return $cookies;
    }
}
openapi-psr7-validator/src/PSR7/Validators/CookiesValidator/ServerRequestCookieValidator.php000064400000003225147361032640026307 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators\CookiesValidator;

use cebe\openapi\spec\Parameter;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidCookies;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidParameter;
use League\OpenAPIValidation\PSR7\Exception\Validation\RequiredParameterMissing;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\Validators\ArrayValidator;
use League\OpenAPIValidation\PSR7\Validators\ValidationStrategy;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ServerRequestInterface;
use Webmozart\Assert\Assert;

class ServerRequestCookieValidator implements MessageValidator
{
    use ValidationStrategy;

    /** @var Parameter[] */
    private $specs;

    /**
     * @param Parameter[] $specs
     */
    public function __construct(array $specs)
    {
        $this->specs = $specs;
    }

    /**
     * @throws InvalidCookies
     */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        Assert::isInstanceOf($message, ServerRequestInterface::class);
        $validator = new ArrayValidator($this->specs);

        try {
            $validator->validateArray($message->getCookieParams(), $this->detectValidationStrategy($message));
        } catch (RequiredParameterMissing $e) {
            throw InvalidCookies::becauseOfMissingRequiredCookie($e->name(), $addr);
        } catch (InvalidParameter $e) {
            throw InvalidCookies::becauseValueDoesNotMatchSchema($e->name(), $e->value(), $addr, $e->getPrevious());
        }
    }
}
openapi-psr7-validator/src/PSR7/Validators/ValidatorChain.php000064400000001317147361032640020116 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use Psr\Http\Message\MessageInterface;

final class ValidatorChain implements MessageValidator
{
    /** @var MessageValidator[] */
    private $validators;

    public function __construct(MessageValidator ...$messageValidators)
    {
        $this->validators = $messageValidators;
    }

    /** {@inheritdoc} */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        foreach ($this->validators as $validator) {
            $validator->validate($addr, $message);
        }
    }
}
openapi-psr7-validator/src/PSR7/Validators/SecurityValidator.php000064400000015724147361032640020712 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Validators;

use cebe\openapi\spec\SecurityRequirement;
use cebe\openapi\spec\SecurityScheme;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidCookies;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidQueryArgs;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidSecurity;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\MessageValidator;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\SpecFinder;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;

use function count;
use function preg_match;
use function sprintf;

final class SecurityValidator implements MessageValidator
{
    private const HEADER_AUTHORIZATION = 'Authorization';
    private const AUTH_PATTERN_BASIC   = '#^Basic #';
    private const AUTH_PATTERN_BEARER  = '#^Bearer #';

    /** @var SpecFinder */
    private $finder;

    public function __construct(SpecFinder $finder)
    {
        $this->finder = $finder;
    }

    /** {@inheritdoc} */
    public function validate(OperationAddress $addr, MessageInterface $message): void
    {
        // Note: Security schemes support OR/AND union
        // That is, security is an array of hashmaps, where each hashmap contains one or more named security schemes.
        // Items in a hashmap are combined using logical AND, and array items are combined using logical OR.
        // Security schemes combined via OR are alternatives – any one can be used in the given context.
        // Security schemes combined via AND must be used simultaneously in the same request.
        // @see https://swagger.io/docs/specification/authentication/
        if (! ($message instanceof ServerRequestInterface)) {
            return;
        }

        $this->validateServerRequest($addr, $message);
    }

    /**
     * @throws ValidationFailed
     */
    private function validateServerRequest(OperationAddress $addr, ServerRequestInterface $request): void
    {
        $securitySpecs = $this->finder->findSecuritySpecs($addr);

        if (! count($securitySpecs)) {
            // no auth needed
            return;
        }

        // OR-union: any of security schemes can match
        foreach ($securitySpecs as $spec) {
            try {
                $this->validateSecurityScheme($addr, $request, $spec);

                return; // this security schema matched, request is valid, stop here
            } catch (ValidationFailed $e) {
                // that security schema did not match
            }
        }

        // no schema matched, that is bad
        throw InvalidSecurity::becauseRequestDidNotMatchAnySchema($addr);
    }

    /**
     * @throws InvalidSecurity
     */
    private function validateSecurityScheme(
        OperationAddress $addr,
        ServerRequestInterface $request,
        SecurityRequirement $spec
    ): void {
        // Here I implement AND-union
        // Each SecurityRequirement contains 1+ security [schema_name=>scopes]
        // Scopes are not used for the purpose of validation

        $securitySchemesSpecs = $this->finder->findSecuritySchemesSpecs();
        foreach ($spec->getSerializableData() as $securitySchemeName => $scopes) {
            if (! isset($securitySchemesSpecs[$securitySchemeName])) {
                throw new InvalidSchema(
                    sprintf("Mentioned security scheme '%s' not found in the given spec", $securitySchemeName)
                );
            }

            $securityScheme = $securitySchemesSpecs[$securitySchemeName];

            try {
                switch ($securityScheme->type) {
                    case 'http':
                        $this->validateHTTPSecurityScheme($addr, $request, $securityScheme);
                        break;
                    case 'apiKey':
                        $this->validateApiKeySecurityScheme($addr, $request, $securityScheme);
                        break;
                }
            } catch (ValidationFailed $exception) {
                throw InvalidSecurity::becauseRequestDidNotMatchSchema($securitySchemeName, $addr, $exception);
            }
        }
    }

    /**
     * @throws ValidationFailed
     */
    private function validateHTTPSecurityScheme(
        OperationAddress $addr,
        RequestInterface $request,
        SecurityScheme $securityScheme
    ): void {
        // Supported schemas: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml

        // Token should be passed in TLS session, in header: `Authorization:....`
        if (! $request->hasHeader(self::HEADER_AUTHORIZATION)) {
            throw InvalidHeaders::becauseOfMissingRequiredHeader(self::HEADER_AUTHORIZATION, $addr);
        }

        switch ($securityScheme->scheme) {
            case 'basic':
                // Described in https://tools.ietf.org/html/rfc7617
                if (! preg_match(self::AUTH_PATTERN_BASIC, $request->getHeader(self::HEADER_AUTHORIZATION)[0])) {
                    throw InvalidSecurity::becauseAuthHeaderValueDoesNotMatchExpectedPattern(
                        self::HEADER_AUTHORIZATION,
                        self::AUTH_PATTERN_BASIC,
                        $addr
                    );
                }

                break;
            case 'bearer':
                // Described in https://tools.ietf.org/html/rfc6750
                if (! preg_match(self::AUTH_PATTERN_BEARER, $request->getHeader(self::HEADER_AUTHORIZATION)[0])) {
                    throw InvalidSecurity::becauseAuthHeaderValueDoesNotMatchExpectedPattern(
                        self::HEADER_AUTHORIZATION,
                        self::AUTH_PATTERN_BEARER,
                        $addr
                    );
                }

                break;
        }
    }

    /**
     * @throws ValidationFailed
     */
    private function validateApiKeySecurityScheme(
        OperationAddress $addr,
        ServerRequestInterface $request,
        SecurityScheme $securityScheme
    ): void {
        switch ($securityScheme->in) {
            case 'query':
                if (! isset($request->getQueryParams()[$securityScheme->name])) {
                    throw InvalidQueryArgs::becauseOfMissingRequiredArgument($securityScheme->name, $addr);
                }

                break;
            case 'header':
                if (! $request->hasHeader($securityScheme->name)) {
                    throw InvalidHeaders::becauseOfMissingRequiredHeader($securityScheme->name, $addr);
                }

                break;
            case 'cookie':
                if (! isset($request->getCookieParams()[$securityScheme->name])) {
                    throw InvalidCookies::becauseOfMissingRequiredCookie($securityScheme->name, $addr);
                }

                break;
        }
    }
}
openapi-psr7-validator/src/PSR7/RoutedServerRequestValidator.php000064400000003147147361032640020771 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\Validators\BodyValidator\BodyValidator;
use League\OpenAPIValidation\PSR7\Validators\CookiesValidator\CookiesValidator;
use League\OpenAPIValidation\PSR7\Validators\HeadersValidator;
use League\OpenAPIValidation\PSR7\Validators\PathValidator;
use League\OpenAPIValidation\PSR7\Validators\QueryArgumentsValidator;
use League\OpenAPIValidation\PSR7\Validators\SecurityValidator;
use League\OpenAPIValidation\PSR7\Validators\ValidatorChain;
use Psr\Http\Message\ServerRequestInterface;

class RoutedServerRequestValidator implements ReusableSchema
{
    /** @var OpenApi */
    protected $openApi;
    /** @var MessageValidator */
    protected $validator;

    public function __construct(OpenApi $schema)
    {
        $this->openApi   = $schema;
        $finder          = new SpecFinder($this->openApi);
        $this->validator = new ValidatorChain(
            new HeadersValidator($finder),
            new CookiesValidator($finder),
            new BodyValidator($finder),
            new QueryArgumentsValidator($finder),
            new PathValidator($finder),
            new SecurityValidator($finder)
        );
    }

    public function getSchema(): OpenApi
    {
        return $this->openApi;
    }

    /**
     * @throws ValidationFailed
     */
    public function validate(OperationAddress $opAddr, ServerRequestInterface $serverRequest): void
    {
        $this->validator->validate($opAddr, $serverRequest);
    }
}
openapi-psr7-validator/src/PSR7/Exception/NoResponseCode.php000064400000001252147361032640017740 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception;

use function sprintf;

class NoResponseCode extends NoOperation
{
    /** @var int */
    protected $responseCode;

    public static function fromPathAndMethodAndResponseCode(string $path, string $method, int $responseCode): self
    {
        $i               = new self(sprintf('OpenAPI spec contains no such operation [%s,%s,%d]', $path, $method, $responseCode));
        $i->path         = $path;
        $i->method       = $method;
        $i->responseCode = $responseCode;

        return $i;
    }

    public function responseCode(): int
    {
        return $this->responseCode;
    }
}
openapi-psr7-validator/src/PSR7/Exception/MultipleOperationsMismatchForRequest.php000064400000002037147361032640024421 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception;

use League\OpenAPIValidation\PSR7\OperationAddress;

use function array_map;
use function implode;
use function sprintf;

class MultipleOperationsMismatchForRequest extends ValidationFailed
{
    /** @var OperationAddress[] */
    protected $matchedAddrs;

    /**
     * @param OperationAddress[] $addrs
     */
    public static function fromMatchedAddrs(array $addrs): self
    {
        $addrsStrings = array_map(static function (OperationAddress $addr) {
            return sprintf('[%s,%s]', $addr->path(), $addr->method());
        }, $addrs);

        $message         = 'The given request matched these operations: %s. However, it matched not a single schema of theirs.';
        $i               = new self(sprintf($message, implode(',', $addrsStrings)));
        $i->matchedAddrs = $addrs;

        return $i;
    }

    /**
     * @return OperationAddress[]
     */
    public function matchedAddrs(): array
    {
        return $this->matchedAddrs;
    }
}
openapi-psr7-validator/src/PSR7/Exception/NoOperation.php000064400000001046147361032640017310 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception;

use function sprintf;

class NoOperation extends NoPath
{
    /** @var string */
    protected $method;

    public static function fromPathAndMethod(string $path, string $method): self
    {
        $i         = new self(sprintf('OpenAPI spec contains no such operation [%s,%s]', $path, $method));
        $i->path   = $path;
        $i->method = $method;

        return $i;
    }

    public function method(): string
    {
        return $this->method;
    }
}
openapi-psr7-validator/src/PSR7/Exception/NoCallback.php000064400000001453147361032640017046 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception;

use function sprintf;

class NoCallback extends NoOperation
{
    /** @var string */
    protected $callbackName;

    /** @var string */
    protected $callbackMethod;

    public static function fromCallbackPath(string $path, string $method, string $callbackName, string $callbackMethod): self
    {
        $i                 = new self(sprintf('OpenAPI spec contains no such callback [%s,%s,%s,%s]', $path, $method, $callbackName, $callbackMethod));
        $i->path           = $path;
        $i->method         = $method;
        $i->callbackName   = $callbackName;
        $i->callbackMethod = $callbackMethod;

        return $i;
    }

    public function method(): string
    {
        return $this->method;
    }
}
openapi-psr7-validator/src/PSR7/Exception/NoContentType.php000064400000000350147361032640017621 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception;

class NoContentType extends ValidationFailed
{
    /** @var string */
    protected $message = "Message's body contains no Content-Type header";
}
openapi-psr7-validator/src/PSR7/Exception/NoPath.php000064400000000736147361032640016251 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception;

use function sprintf;

class NoPath extends ValidationFailed
{
    /** @var string */
    protected $path;

    public static function fromPath(string $path): self
    {
        $i       = new self(sprintf('OpenAPI spec contains no such operation [%s]', $path));
        $i->path = $path;

        return $i;
    }

    public function path(): string
    {
        return $this->path;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/RequiredParameterMissing.php000064400000000775147361032640024130 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;

class RequiredParameterMissing extends ValidationFailed
{
    /** @var string */
    protected $name;

    public static function fromName(string $name): self
    {
        $exception       = new self();
        $exception->name = $name;

        return $exception;
    }

    public function name(): string
    {
        return $this->name;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidParameter.php000064400000002267147361032640022402 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;

use function is_scalar;
use function json_encode;
use function sprintf;

use const JSON_PRETTY_PRINT;

/**
 * @method SchemaMismatch getPrevious()
 */
class InvalidParameter extends ValidationFailed
{
    /** @var string */
    protected $name;

    /** @var string */
    protected $value;

    /**
     * @param mixed $value
     *
     * @return InvalidParameter
     */
    public static function becauseValueDidNotMatchSchema(string $name, $value, SchemaMismatch $prev): self
    {
        if (! is_scalar($value)) {
            $value = json_encode($value, JSON_PRETTY_PRINT);
        }

        $exception        = new self(sprintf("Parameter '%s' has invalid value '%s'", $name, $value), 0, $prev);
        $exception->name  = $name;
        $exception->value = (string) $value;

        return $exception;
    }

    public function name(): string
    {
        return $this->name;
    }

    public function value(): string
    {
        return (string) $this->value;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidPath.php000064400000001717147361032640021355 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\OperationAddress;
use Throwable;

use function sprintf;

class InvalidPath extends AddressValidationFailed
{
    public static function becauseValueDoesNotMatchSchema(string $parameterName, string $parameterValue, OperationAddress $address, Throwable $prev): self
    {
        $exception          = static::fromAddrAndPrev($address, $prev);
        $exception->message = sprintf('Value "%s" for parameter "%s" is invalid for %s', $parameterValue, $parameterName, $address);

        return $exception;
    }

    public static function becausePathDoesNotMatchPattern(string $path, OperationAddress $address): self
    {
        $exception          = static::fromAddr($address);
        $exception->message = sprintf('Unable to parse "%s" against the pattern "%s" for %s', $path, $address->path(), $address);

        return $exception;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidQueryArgs.php000064400000002615147361032640022401 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\OperationAddress;
use Throwable;

use function sprintf;

class InvalidQueryArgs extends AddressValidationFailed
{
    public static function becauseOfMissingRequiredArgument(string $argumentName, OperationAddress $address, ?Throwable $prev = null): self
    {
        if ($prev !== null) {
            $exception = static::fromAddrAndPrev($address, $prev);
        } else {
            $exception = static::fromAddr($address);
        }

        $exception->message = sprintf('Missing required argument "%s" for %s', $argumentName, $address);

        return $exception;
    }

    public static function becauseValueDoesNotMatchSchema(string $argumentName, string $argumentValue, OperationAddress $address, Throwable $prev): self
    {
        $exception          = static::fromAddrAndPrev($address, $prev);
        $exception->message = sprintf('Value "%s" for argument "%s" is invalid for %s', $argumentValue, $argumentName, $address);

        return $exception;
    }

    public static function becauseOfUnexpectedArgumentIsNotAllowed(string $argument, OperationAddress $address): self
    {
        $exception          = static::fromAddr($address);
        $exception->message = sprintf('Argument "%s" is not allowed for %s', $argument, $address);

        return $exception;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidBody.php000064400000002754147361032640021360 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;

use function sprintf;

class InvalidBody extends AddressValidationFailed
{
    public static function becauseBodyDoesNotMatchSchema(
        string $contentType,
        OperationAddress $addr,
        SchemaMismatch $prev
    ): self {
        $exception          = static::fromAddrAndPrev($addr, $prev);
        $exception->message = sprintf('Body does not match schema for content-type "%s" for %s', $contentType, $addr);

        return $exception;
    }

    public static function becauseBodyDoesNotMatchSchemaMultipart(
        string $partName,
        string $contentType,
        OperationAddress $addr,
        ?SchemaMismatch $prev = null
    ): self {
        $exception          = $prev ? static::fromAddrAndPrev($addr, $prev) : static::fromAddr($addr);
        $exception->message = sprintf(
            'Multipart body does not match schema for part "%s" with content-type "%s" for %s',
            $partName,
            $contentType,
            $addr
        );

        return $exception;
    }

    public static function becauseBodyIsNotValidJson(string $error, OperationAddress $addr): self
    {
        $exception          = static::fromAddr($addr);
        $exception->message = sprintf('JSON parsing failed with "%s" for %s', $error, $addr);

        return $exception;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/AddressValidationFailed.php000064400000002262147361032640023653 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\OperationAddress;
use Throwable;

use function sprintf;

abstract class AddressValidationFailed extends ValidationFailed
{
    /** @var OperationAddress */
    private $address;

    final public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }

    /**
     * @return static
     */
    public static function fromAddrAndPrev(OperationAddress $address, Throwable $prev): self
    {
        $ex          = new static(sprintf('Validation failed for %s', $address), $prev->getCode(), $prev);
        $ex->address = $address;

        return $ex;
    }

    /**
     * @return static
     */
    public static function fromAddr(OperationAddress $address): self
    {
        $ex          = new static(sprintf('Validation failed for %s', $address));
        $ex->address = $address;

        return $ex;
    }

    public function getAddress(): OperationAddress
    {
        return $this->address;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidCookies.php000064400000001740147361032640022051 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;

use function sprintf;

class InvalidCookies extends AddressValidationFailed
{
    public static function becauseOfMissingRequiredCookie(string $cookieName, OperationAddress $address): self
    {
        $exception          = static::fromAddr($address);
        $exception->message = sprintf('Missing required cookie "%s" for %s', $cookieName, $address);

        return $exception;
    }

    public static function becauseValueDoesNotMatchSchema(string $cookieName, string $cookieValue, OperationAddress $address, SchemaMismatch $prev): self
    {
        $exception          = static::fromAddrAndPrev($address, $prev);
        $exception->message = sprintf('Value "%s" for cookie "%s" is invalid for %s', $cookieValue, $cookieName, $address);

        return $exception;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidHeaders.php000064400000004676147361032640022043 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;

use function sprintf;

class InvalidHeaders extends AddressValidationFailed
{
    public static function becauseOfMissingRequiredHeader(string $headerName, OperationAddress $address): self
    {
        $exception          = static::fromAddr($address);
        $exception->message = sprintf('Missing required header "%s" for %s', $headerName, $address);

        return $exception;
    }

    public static function becauseOfMissingRequiredHeaderMupripart(
        string $partName,
        string $headerName,
        OperationAddress $address
    ): self {
        $exception          = static::fromAddr($address);
        $exception->message = sprintf('Missing required header "%s" for %s in multipart "%s"', $headerName, $address, $partName);

        return $exception;
    }

    public static function becauseValueDoesNotMatchSchema(string $headerName, string $headerValue, OperationAddress $address, SchemaMismatch $prev): self
    {
        $exception          = static::fromAddrAndPrev($address, $prev);
        $exception->message = sprintf('Value "%s" for header "%s" is invalid for %s', $headerValue, $headerName, $address);

        return $exception;
    }

    public static function becauseValueDoesNotMatchSchemaMultipart(
        string $partName,
        string $headerName,
        string $headerValue,
        OperationAddress $address,
        SchemaMismatch $prev
    ): self {
        $exception          = static::fromAddrAndPrev($address, $prev);
        $exception->message = sprintf('Value "%s" for header "%s" is invalid for "%s" in multipart "%s"', $headerValue, $headerName, $address, $partName);

        return $exception;
    }

    public static function becauseOfUnexpectedHeaderIsNotAllowed(string $header, OperationAddress $address): self
    {
        $exception          = static::fromAddr($address);
        $exception->message = sprintf('Header "%s" is not allowed for %s', $header, $address);

        return $exception;
    }

    public static function becauseContentTypeIsNotExpected(string $contentType, OperationAddress $addr): self
    {
        $exception          = static::fromAddr($addr);
        $exception->message = sprintf('Content-Type "%s" is not expected for %s', $contentType, $addr);

        return $exception;
    }
}
openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidSecurity.php000064400000002402147361032640022260 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception\Validation;

use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\OperationAddress;

use function sprintf;

class InvalidSecurity extends AddressValidationFailed
{
    public static function becauseAuthHeaderValueDoesNotMatchExpectedPattern(string $header, string $pattern, OperationAddress $addr): self
    {
        $exception          = static::fromAddr($addr);
        $exception->message = sprintf('Header "%s" should match pattern "%s" for %s', $header, $pattern, $addr);

        return $exception;
    }

    public static function becauseRequestDidNotMatchAnySchema(OperationAddress $addr): self
    {
        $exception          = static::fromAddr($addr);
        $exception->message = sprintf('None of security schemas did match for %s', $addr);

        return $exception;
    }

    public static function becauseRequestDidNotMatchSchema(string $securitySchemeName, OperationAddress $addr, ValidationFailed $prev): self
    {
        $exception          = static::fromAddrAndPrev($addr, $prev);
        $exception->message = sprintf('Security schema "%s" did not match for %s', $securitySchemeName, $addr);

        return $exception;
    }
}
openapi-psr7-validator/src/PSR7/Exception/ValidationFailed.php000064400000000222147361032640020245 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7\Exception;

use Exception;

class ValidationFailed extends Exception
{
}
openapi-psr7-validator/src/PSR7/ValidatorBuilder.php000064400000010226147361032640016351 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use InvalidArgumentException;
use League\OpenAPIValidation\PSR7\SchemaFactory\JsonFactory;
use League\OpenAPIValidation\PSR7\SchemaFactory\JsonFileFactory;
use League\OpenAPIValidation\PSR7\SchemaFactory\PrecreatedSchemaFactory;
use League\OpenAPIValidation\PSR7\SchemaFactory\YamlFactory;
use League\OpenAPIValidation\PSR7\SchemaFactory\YamlFileFactory;
use Psr\Cache\CacheItemPoolInterface;

class ValidatorBuilder
{
    /** @var SchemaFactory */
    protected $factory;
    /** @var CacheItemPoolInterface */
    protected $cache;
    /** @var int|null */
    protected $ttl;
    /** @var string */
    protected $cacheKey;

    /**
     * @return $this
     */
    public function setCache(CacheItemPoolInterface $cache, ?int $ttl = null): self
    {
        $this->cache = $cache;
        $this->ttl   = $ttl;

        return $this;
    }

    /**
     * @return $this
     */
    public function overrideCacheKey(string $cacheKey): self
    {
        $this->cacheKey = $cacheKey;

        return $this;
    }

    /**
     * @return $this
     */
    public function fromYaml(string $yaml): self
    {
        $this->setSchemaFactory(new YamlFactory($yaml));

        return $this;
    }

    /**
     * @return $this
     */
    public function setSchemaFactory(SchemaFactory $schemaFactory): self
    {
        $this->factory = $schemaFactory;

        return $this;
    }

    /**
     * @return $this
     */
    public function fromYamlFile(string $yamlFile): self
    {
        $this->setSchemaFactory(new YamlFileFactory($yamlFile));

        return $this;
    }

    /**
     * @return $this
     */
    public function fromJson(string $json): self
    {
        $this->setSchemaFactory(new JsonFactory($json));

        return $this;
    }

    /**
     * @return $this
     */
    public function fromJsonFile(string $jsonFile): self
    {
        $this->setSchemaFactory(new JsonFileFactory($jsonFile));

        return $this;
    }

    /**
     * @return $this
     */
    public function fromSchema(OpenApi $schema): self
    {
        $this->setSchemaFactory(new PrecreatedSchemaFactory($schema));

        return $this;
    }

    public function getServerRequestValidator(): ServerRequestValidator
    {
        $schema = $this->getOrCreateSchema();

        return new ServerRequestValidator($schema);
    }

    public function getRequestValidator(): RequestValidator
    {
        $schema = $this->getOrCreateSchema();

        return new RequestValidator($schema);
    }

    public function getResponseValidator(): ResponseValidator
    {
        return new ResponseValidator($this->getOrCreateSchema());
    }

    public function getRoutedRequestValidator(): RoutedServerRequestValidator
    {
        return new RoutedServerRequestValidator($this->getOrCreateSchema());
    }

    public function getCallbackRequestValidator(): CallbackRequestValidator
    {
        return new CallbackRequestValidator($this->getOrCreateSchema());
    }

    public function getCallbackResponseValidator(): CallbackResponseValidator
    {
        return new CallbackResponseValidator($this->getOrCreateSchema());
    }

    protected function getOrCreateSchema(): OpenApi
    {
        // Make cache dependency optional for end user
        if ($this->cache === null) {
            return $this->factory->createSchema();
        }

        $cacheKey = $this->getCacheKey();

        $item = $this->cache->getItem($cacheKey);

        if ($item->isHit()) {
            return $item->get();
        }

        $schema = $this->factory->createSchema();

        $item->set($schema);
        $item->expiresAfter($this->ttl);

        $this->cache->save($item);

        return $schema;
    }

    protected function getCacheKey(): string
    {
        if ($this->cacheKey !== null) {
            return $this->cacheKey;
        }

        if (! $this->factory instanceof CacheableSchemaFactory) {
            throw new InvalidArgumentException(
                'Either provide cache key manually or use instance of ' . CacheableSchemaFactory::class
            );
        }

        return $this->factory->getCacheKey();
    }
}
openapi-psr7-validator/src/PSR7/OperationAddress.php000064400000007600147361032640016365 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidPath;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;

use function implode;
use function preg_match;
use function preg_quote;
use function preg_replace;
use function preg_split;
use function sprintf;
use function strtok;

use const PREG_SPLIT_DELIM_CAPTURE;

class OperationAddress
{
    private const PATH_PLACEHOLDER = '#{[^}]+}#';

    /** @var string */
    protected $method;
    /** @var string */
    protected $path;

    public function __construct(string $path, string $method)
    {
        $this->path   = $path;
        $this->method = $method;
    }

    /**
     * Checks if path matches a specification
     *
     * @param string $specPath like "/users/{id}"
     * @param string $path     like "/users/12"
     */
    public static function isPathMatchesSpec(string $specPath, string $path): bool
    {
        $pattern = '#^' . preg_replace(self::PATH_PLACEHOLDER, '[^/]+', $specPath) . '/?$#';

        return (bool) preg_match($pattern, $path);
    }

    public function method(): string
    {
        return $this->method;
    }

    public function __toString(): string
    {
        return sprintf('Request [%s %s]', $this->method, $this->path);
    }

    public function path(): string
    {
        return $this->path;
    }

    public function hasPlaceholders(): bool
    {
        return preg_match(self::PATH_PLACEHOLDER, $this->path()) === 1;
    }

    /**
     * Parses given URL and returns params according to the pattern.
     *
     * Example:
     * $specPath = "/users/{id}";
     * $url = "/users/12";
     * returns ["id"=>12]
     *
     * @param string $url as seen in actual HTTP Request/ServerRequest
     *
     * @return mixed[] return array of ["paramName"=>"parsedValue", ...]
     *
     * @throws InvalidPath
     */
    public function parseParams(string $url): array
    {
        // pattern: /a/{b}/c/{d}
        // actual:  /a/12/c/some
        // result:  ['b'=>'12', 'd'=>'some']

        // 0. Filter URL, remove query string
        $url = strtok($url, '?');

        // 1. Find param names and build pattern
        $pattern = $this->buildPattern($this->path(), $parameterNames);

        // 2. Parse param values
        if (! preg_match($pattern, $url, $matches)) {
            throw InvalidPath::becausePathDoesNotMatchPattern($url, $this);
        }

        // 3. Combine keys and values
        $parsedParams = [];
        foreach ($parameterNames as $name) {
            $parsedParams[$name] = $matches[$name];
        }

        return $parsedParams;
    }

    /**
     * It builds PCRE pattern, which can be used to parse path. It also extract parameter names
     *
     * @param array<string>|null $parameterNames
     */
    protected function buildPattern(string $url, ?array &$parameterNames): string
    {
        $parameterNames = [];
        $pregParts      = [];
        $inParameter    = false;

        $parts = preg_split('#([{}])#', $url, -1, PREG_SPLIT_DELIM_CAPTURE);
        foreach ($parts as $part) {
            switch ($part) {
                case '{':
                    if ($inParameter) {
                        throw InvalidSchema::becauseBracesAreNotBalanced($url);
                    }

                    $inParameter = true;
                    continue 2;
                case '}':
                    if (! $inParameter) {
                        throw InvalidSchema::becauseBracesAreNotBalanced($url);
                    }

                    $inParameter = false;
                    continue 2;
            }

            if ($inParameter) {
                $pregParts[]      = '(?<' . $part . '>[^/]+)';
                $parameterNames[] = $part;
            } else {
                $pregParts[] = preg_quote($part, '#');
            }
        }

        return '#' . implode($pregParts) . '#';
    }
}
openapi-psr7-validator/src/PSR7/RequestValidator.php000064400000007127147361032640016421 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use League\OpenAPIValidation\PSR7\Exception\MultipleOperationsMismatchForRequest;
use League\OpenAPIValidation\PSR7\Exception\NoOperation;
use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\Validators\BodyValidator\BodyValidator;
use League\OpenAPIValidation\PSR7\Validators\CookiesValidator\CookiesValidator;
use League\OpenAPIValidation\PSR7\Validators\HeadersValidator;
use League\OpenAPIValidation\PSR7\Validators\PathValidator;
use League\OpenAPIValidation\PSR7\Validators\QueryArgumentsValidator;
use League\OpenAPIValidation\PSR7\Validators\SecurityValidator;
use League\OpenAPIValidation\PSR7\Validators\ValidatorChain;
use Psr\Http\Message\RequestInterface;
use Throwable;

use function count;
use function strtolower;

class RequestValidator implements ReusableSchema
{
    /** @var OpenApi */
    protected $openApi;
    /** @var MessageValidator */
    protected $validator;

    public function __construct(OpenApi $schema)
    {
        $this->openApi   = $schema;
        $finder          = new SpecFinder($this->openApi);
        $this->validator = new ValidatorChain(
            new HeadersValidator($finder),
            new CookiesValidator($finder),
            new BodyValidator($finder),
            new QueryArgumentsValidator($finder),
            new PathValidator($finder),
            new SecurityValidator($finder)
        );
    }

    public function getSchema(): OpenApi
    {
        return $this->openApi;
    }

    /**
     * @return OperationAddress which matched the Request
     *
     * @throws ValidationFailed
     */
    public function validate(RequestInterface $request): OperationAddress
    {
        $path   = $request->getUri()->getPath();
        $method = strtolower($request->getMethod());

        $pathFinder = new PathFinder($this->openApi, (string) $request->getUri(), $request->getMethod());

        // 0. Find matching operations
        // If there is only one - then proceed with checking
        // If there are multiple candidates, then check each one, if all fail - we don't know which one supposed to be the one, so we need to throw an exception like
        // "This request matched operations A,B and C, but mismatched its schemas."
        $matchingOperationsAddrs = $pathFinder->search();

        if (! $matchingOperationsAddrs) {
            if ($pathFinder->getPathMatches() !== []) {
                throw NoOperation::fromPathAndMethod($path, $method);
            }

            throw NoPath::fromPath($path);
        }

        // Single match is the most desirable variant, because we reduce ambiguity down to zero
        if (count($matchingOperationsAddrs) === 1) {
            $this->validator->validate($matchingOperationsAddrs[0], $request);

            return $matchingOperationsAddrs[0];
        }

        // there are multiple matching operations, this is bad, because if none of them match the message
        // then we cannot say reliably which one intended to match
        foreach ($matchingOperationsAddrs as $matchedAddr) {
            try {
                $this->validator->validate($matchedAddr, $request);

                return $matchedAddr; // Good, operation matched and request is valid against it, stop here
            } catch (Throwable $e) {
                // that operation did not match
            }
        }

        // no operation matched at all...
        throw MultipleOperationsMismatchForRequest::fromMatchedAddrs($matchingOperationsAddrs);
    }
}
openapi-psr7-validator/src/PSR7/PathFinder.php000064400000014305147361032640015143 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use cebe\openapi\spec\PathItem;
use cebe\openapi\spec\Server;

use function count;
use function ltrim;
use function parse_url;
use function preg_match;
use function preg_replace;
use function rtrim;
use function sprintf;
use function strtolower;
use function usort;

use const PHP_URL_PATH;

// This class finds operations matching the given URI+method
// That would be a very simple operation if there were no "Servers" keyword.
// We need to take into account possible base-url case (and its templating feature)
// @see https://swagger.io/docs/specification/api-host-and-base-path/
//
// More: as discussed here https://github.com/lezhnev74/openapi-psr7-validator/issues/32
// "schema://hostname" should not be included in the validation process (assume any hostname matches)
class PathFinder
{
    /** @var OpenApi */
    protected $openApiSpec;
    /** @var string */
    protected $path;
    /** @var string $method like "get" */
    protected $method;
    /** @var OperationAddress[] */
    protected $searchResult;

    public function __construct(OpenApi $openApiSpec, string $uri, string $method)
    {
        $this->openApiSpec = $openApiSpec;
        $this->path        = (string) parse_url($uri, PHP_URL_PATH);
        $this->method      = strtolower($method);
    }

    /**
     * Determine matching paths.
     *
     * @return PathItem[]
     */
    public function getPathMatches(): array
    {
        // Determine if path matches exactly.
        $match = $this->openApiSpec->paths->getPath($this->path);
        if ($match !== null) {
            return [$match];
        }

        // Probably path is parametrized or matches partially. Determine candidates and try to match path.
        $matches = [];
        foreach ($this->search() as $result) {
            $matches[] = $this->openApiSpec->paths->getPath($result->path());
        }

        return $matches;
    }

    /**
     * @return OperationAddress[]
     */
    public function search(): array
    {
        if ($this->searchResult === null) {
            $this->searchResult = $this->doSearch();
        }

        return $this->searchResult;
    }

    /**
     * @return OperationAddress[]
     */
    private function doSearch(): array
    {
        $paths = [];

        // 1. Find operations which match criteria
        $opCandidates = $this->searchForCandidates();

        // 2. for each operation, find suitable "servers" (with respect to overriding)
        foreach ($opCandidates as $i => $opAddress) {
            $opCandidates[$i] = [
                'addr' => $opAddress,
                'servers' => $this->findServersForOperation($opAddress),
            ];
        }

        // 3. Check each candidate operation against it's servers
        foreach ($opCandidates as $opCandidate) {
            /** @var Server $server */
            foreach ($opCandidate['servers'] as $server) {
                $candidatePath = $this->composeFullOperationPath($server, $opCandidate['addr']);

                // 3.1 Compare this path against the real/given path
                if (! OperationAddress::isPathMatchesSpec($candidatePath, $this->path)) {
                    continue;
                }

                // path matched!
                $paths[] = $opCandidate['addr'];
                break;
            }
        }

        return $this->prioritizeStaticPaths($paths);
    }

    /**
     * Find operations which in general match the request:
     * 1. path ends with the same given path (so there can be some prefixes in servers)
     * 2. method matches
     *
     * @return OperationAddress[]
     */
    private function searchForCandidates(): array
    {
        $matchedOperations = [];

        foreach ($this->openApiSpec->paths as $specPath => $pathItemSpec) {
            // 1. path ends with the same given path (so there can be some prefixes in servers)
            // like
            // $this->path: /v1/users/admin
            // specPath:       /users/{group}
            // servers:     /v1
            $pattern = '#' . preg_replace('#{[^}]+}#', '[^/]+', $specPath) . '/?$#';

            if (! (bool) preg_match($pattern, $this->path)) {
                continue;
            }

            // 2. method matches
            foreach ($pathItemSpec->getOperations() as $opMethod => $operation) {
                if ($opMethod !== $this->method) {
                    continue;
                }

                // ok looks like method and path matched
                $matchedOperations[] = new OperationAddress($specPath, $opMethod);
            }
        }

        return $matchedOperations;
    }

    /**
     * The global servers array can be overridden on the path level or operation level.
     *
     * @return Server[]
     */
    private function findServersForOperation(OperationAddress $opAddress): array
    {
        $path      = $this->openApiSpec->paths->getPath($opAddress->path());
        $operation = $path->getOperations()[$opAddress->method()];

        // 1. Check servers on operation level
        if (isset($operation->servers) && count($operation->servers) > 0) {
            return $operation->servers;
        }

        // 2. Check servers on path level
        if (isset($path->servers) && count($path->servers) > 0) {
            return $path->servers;
        }

        // 3. Fallback with servers on root level
        return $this->openApiSpec->servers;
    }

    private function composeFullOperationPath(Server $server, OperationAddress $addr): string
    {
        return sprintf(
            '%s/%s',
            rtrim((string) parse_url($server->url, PHP_URL_PATH), '/'),
            ltrim($addr->path(), '/')
        );
    }

    /**
     * @param OperationAddress[] $paths
     *
     * @return OperationAddress[]
     */
    private function prioritizeStaticPaths(array $paths): array
    {
        usort($paths, static function (OperationAddress $a, OperationAddress $b): int {
            if ($a->hasPlaceholders() && ! $b->hasPlaceholders()) {
                return 1;
            }

            if ($b->hasPlaceholders() && ! $a->hasPlaceholders()) {
                return -1;
            }

            return 0;
        });

        return $paths;
    }
}
openapi-psr7-validator/src/PSR7/CallbackAddress.php000064400000001562147361032640016122 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR7;

use function sprintf;

class CallbackAddress extends OperationAddress
{
    /** @var string */
    protected $callbackName;

    /** @var string */
    protected $callbackMethod;

    public function __construct(string $path, string $method, string $callbackName, string $callbackMethod)
    {
        parent::__construct($path, $method);
        $this->callbackName   = $callbackName;
        $this->callbackMethod = $callbackMethod;
    }

    public function callbackName(): string
    {
        return $this->callbackName;
    }

    public function callbackMethod(): string
    {
        return $this->callbackMethod;
    }

    public function __toString(): string
    {
        return sprintf('Callback [%s %s %s %s]', $this->method, $this->path, $this->callbackName, $this->callbackMethod);
    }
}
openapi-psr7-validator/src/Foundation/ArrayHelper.php000064400000001317147361032640016707 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Foundation;

use function array_keys;
use function count;
use function range;

final class ArrayHelper
{
    /**
     * Check if array has non-numeric keys
     *
     * JSON's objects and arrays are caster to PHP arrays.
     * To distinguish the two it evaluates keys of PHP array:
     * - if there are only numeric keys (0...N) then it returns true
     * - otherwise, if there are string keys it returns false
     *
     * @param mixed[] $arr
     */
    public static function isAssoc(array $arr): bool
    {
        if ($arr === []) {
            return false;
        }

        return array_keys($arr) !== range(0, count($arr) - 1);
    }
}
openapi-psr7-validator/src/PSR15/ValidationMiddleware.php000064400000003647147361032640017275 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR15;

use League\OpenAPIValidation\PSR15\Exception\InvalidResponseMessage;
use League\OpenAPIValidation\PSR15\Exception\InvalidServerRequestMessage;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
use League\OpenAPIValidation\PSR7\ResponseValidator;
use League\OpenAPIValidation\PSR7\ServerRequestValidator;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class ValidationMiddleware implements MiddlewareInterface
{
    /** @var ServerRequestValidator */
    private $requestValidator;
    /** @var ResponseValidator */
    private $responseValidator;

    public function __construct(ServerRequestValidator $requestValidator, ResponseValidator $responseValidator)
    {
        $this->requestValidator  = $requestValidator;
        $this->responseValidator = $responseValidator;
    }

    /**
     * Process an incoming server request.
     *
     * Processes an incoming server request in order to produce a response.
     * If unable to produce the response itself, it may delegate to the provided
     * request handler to do so.
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 1. Validate request
        try {
            $matchedOASOperation = $this->requestValidator->validate($request);
        } catch (ValidationFailed $e) {
            throw InvalidServerRequestMessage::because($e);
        }

        // 2. Process request
        $response = $handler->handle($request);

        // 3. Validate response
        try {
            $this->responseValidator->validate($matchedOASOperation, $response);
        } catch (ValidationFailed $e) {
            throw InvalidResponseMessage::because($e);
        }

        return $response;
    }
}
openapi-psr7-validator/src/PSR15/ValidationMiddlewareBuilder.php000064400000000706147361032640020575 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR15;

use League\OpenAPIValidation\PSR7\ValidatorBuilder;
use Psr\Http\Server\MiddlewareInterface;

class ValidationMiddlewareBuilder extends ValidatorBuilder
{
    public function getValidationMiddleware(): MiddlewareInterface
    {
        return new ValidationMiddleware(
            $this->getServerRequestValidator(),
            $this->getResponseValidator()
        );
    }
}
openapi-psr7-validator/src/PSR15/SlimAdapter.php000064400000002346147361032640015405 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR15;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
 * This class wraps a PSR-15 style single pass middleware,
 * into an invokable double pass middleware.
 */
final class SlimAdapter implements RequestHandlerInterface
{
    /** @var MiddlewareInterface */
    private $middleware;
    /** @var ResponseInterface */
    private $response;
    /** @var callable */
    private $next;

    public function __construct(MiddlewareInterface $middleware)
    {
        $this->middleware = $middleware;
    }

    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
    {
        $this->response = $response;
        $this->next     = $next;

        /* Call the PSR-15 middleware and let it return to our handle()
         * method by passing `$this` as RequestHandler. */
        return $this->middleware->process($request, $this);
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return ($this->next)($request, $this->response);
    }
}
openapi-psr7-validator/src/PSR15/Exception/InvalidResponseMessage.php000064400000000537147361032640021550 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR15\Exception;

use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;

class InvalidResponseMessage extends ValidationFailed
{
    public static function because(ValidationFailed $e): self
    {
        return new self('Response message failed validation', 0, $e);
    }
}
openapi-psr7-validator/src/PSR15/Exception/InvalidRequestMessage.php000064400000000535147361032640021400 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR15\Exception;

use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;

class InvalidRequestMessage extends ValidationFailed
{
    public static function because(ValidationFailed $e): self
    {
        return new self('Request message failed validation', 0, $e);
    }
}
openapi-psr7-validator/src/PSR15/Exception/InvalidServerRequestMessage.php000064400000000552147361032640022566 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\PSR15\Exception;

use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;

class InvalidServerRequestMessage extends ValidationFailed
{
    public static function because(ValidationFailed $e): self
    {
        return new self('Server Request message failed validation', 0, $e);
    }
}
openapi-psr7-validator/src/Schema/SchemaValidator.php000064400000015445147361032640016640 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema;

use cebe\openapi\spec\Schema as CebeSchema;
use cebe\openapi\spec\Type as CebeType;
use League\OpenAPIValidation\Foundation\ArrayHelper;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\Keywords\AllOf;
use League\OpenAPIValidation\Schema\Keywords\AnyOf;
use League\OpenAPIValidation\Schema\Keywords\Enum;
use League\OpenAPIValidation\Schema\Keywords\Items;
use League\OpenAPIValidation\Schema\Keywords\Maximum;
use League\OpenAPIValidation\Schema\Keywords\MaxItems;
use League\OpenAPIValidation\Schema\Keywords\MaxLength;
use League\OpenAPIValidation\Schema\Keywords\MaxProperties;
use League\OpenAPIValidation\Schema\Keywords\Minimum;
use League\OpenAPIValidation\Schema\Keywords\MinItems;
use League\OpenAPIValidation\Schema\Keywords\MinLength;
use League\OpenAPIValidation\Schema\Keywords\MinProperties;
use League\OpenAPIValidation\Schema\Keywords\MultipleOf;
use League\OpenAPIValidation\Schema\Keywords\Not;
use League\OpenAPIValidation\Schema\Keywords\Nullable;
use League\OpenAPIValidation\Schema\Keywords\OneOf;
use League\OpenAPIValidation\Schema\Keywords\Pattern;
use League\OpenAPIValidation\Schema\Keywords\Properties;
use League\OpenAPIValidation\Schema\Keywords\Required;
use League\OpenAPIValidation\Schema\Keywords\Type;
use League\OpenAPIValidation\Schema\Keywords\UniqueItems;

use function count;
use function is_array;

// This will load a whole schema and data to validate if one matches another
final class SchemaValidator implements Validator
{
    // How to treat the data (affects writeOnly/readOnly keywords)
    public const VALIDATE_AS_REQUEST  = 0;
    public const VALIDATE_AS_RESPONSE = 1;

    /** @var int strategy of validation - Request or Response (affected by writeOnly/readOnly keywords) */
    private $validationStrategy;

    public function __construct(int $validationStrategy = self::VALIDATE_AS_RESPONSE)
    {
        \Respect\Validation\Validator::in([self::VALIDATE_AS_REQUEST, self::VALIDATE_AS_RESPONSE])->assert($validationStrategy);

        $this->validationStrategy = $validationStrategy;
    }

    /** {@inheritdoc} */
    public function validate($data, CebeSchema $schema, ?BreadCrumb $breadCrumb = null): void
    {
        $breadCrumb = $breadCrumb ?? new BreadCrumb();

        try {
            // These keywords are not part of the JSON Schema at all (new to OAS)
            (new Nullable($schema))->validate($data, $schema->nullable);

            // We don't want to validate any more if the value is a valid Null
            if ($data === null) {
                return;
            }

            // The following properties are taken from the JSON Schema definition but their definitions were adjusted to the OpenAPI Specification.
            if (isset($schema->type)) {
                (new Type($schema))->validate($data, $schema->type, $schema->format);
            }

            // This keywords come directly from JSON Schema Validation, they are the same as in JSON schema
            // https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5
            if (isset($schema->multipleOf)) {
                (new MultipleOf($schema))->validate($data, $schema->multipleOf);
            }

            if (isset($schema->maximum)) {
                $exclusiveMaximum = (bool) ($schema->exclusiveMaximum ?? false);
                (new Maximum($schema))->validate($data, $schema->maximum, $exclusiveMaximum);
            }

            if (isset($schema->minimum)) {
                $exclusiveMinimum = (bool) ($schema->exclusiveMinimum ?? false);
                (new Minimum($schema))->validate($data, $schema->minimum, $exclusiveMinimum);
            }

            if (isset($schema->maxLength)) {
                (new MaxLength($schema))->validate($data, $schema->maxLength);
            }

            if (isset($schema->minLength)) {
                (new MinLength($schema))->validate($data, $schema->minLength);
            }

            if (isset($schema->pattern)) {
                (new Pattern($schema))->validate($data, $schema->pattern);
            }

            if (isset($schema->maxItems)) {
                (new MaxItems($schema))->validate($data, $schema->maxItems);
            }

            if (isset($schema->minItems)) {
                (new MinItems($schema))->validate($data, $schema->minItems);
            }

            if (isset($schema->uniqueItems)) {
                (new UniqueItems($schema))->validate($data, $schema->uniqueItems);
            }

            if (isset($schema->maxProperties)) {
                (new MaxProperties($schema))->validate($data, $schema->maxProperties);
            }

            if (isset($schema->minProperties)) {
                (new MinProperties($schema))->validate($data, $schema->minProperties);
            }

            if (isset($schema->required)) {
                (new Required($schema, $this->validationStrategy, $breadCrumb))->validate($data, $schema->required);
            }

            if (isset($schema->enum)) {
                (new Enum($schema))->validate($data, $schema->enum);
            }

            if (isset($schema->items)) {
                (new Items($schema, $this->validationStrategy, $breadCrumb))->validate($data, $schema->items);
            }

            if (
                $schema->type === CebeType::OBJECT
                || (isset($schema->properties) && is_array($data) && ArrayHelper::isAssoc($data))
            ) {
                $additionalProperties = $schema->additionalProperties ?? null; // defaults to true
                if ((isset($schema->properties) && count($schema->properties)) || $additionalProperties) {
                    (new Properties($schema, $this->validationStrategy, $breadCrumb))->validate(
                        $data,
                        $schema->properties,
                        $additionalProperties
                    );
                }
            }

            if (isset($schema->allOf) && count($schema->allOf)) {
                (new AllOf($schema, $this->validationStrategy, $breadCrumb))->validate($data, $schema->allOf);
            }

            if (isset($schema->oneOf) && count($schema->oneOf)) {
                (new OneOf($schema, $this->validationStrategy, $breadCrumb))->validate($data, $schema->oneOf);
            }

            if (isset($schema->anyOf) && count($schema->anyOf)) {
                (new AnyOf($schema, $this->validationStrategy, $breadCrumb))->validate($data, $schema->anyOf);
            }

            if (isset($schema->not)) {
                (new Not($schema, $this->validationStrategy, $breadCrumb))->validate($data, $schema->not);
            }
            //   ✓  ok, all checks are done
        } catch (SchemaMismatch $e) {
            $e->hydrateDataBreadCrumb($breadCrumb);

            throw $e;
        }
    }
}
openapi-psr7-validator/src/Schema/Validator.php000064400000000621147361032640015505 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema;

use cebe\openapi\spec\Schema;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;

interface Validator
{
    /**
     * @param mixed $data
     *
     * @throws SchemaMismatch if data does not match given schema.
     */
    public function validate($data, Schema $schema, ?BreadCrumb $breadCrumb = null): void;
}
openapi-psr7-validator/src/Schema/TypeFormats/None.php000064400000000442147361032640016735 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

// This format is used for non-meaningful formats like int64,int32
class None
{
    /**
     * @param mixed $value
     */
    public function __invoke($value): bool
    {
        return true;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/NumberDouble.php000064400000000544147361032640020424 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function is_float;
use function is_int;

class NumberDouble
{
    /**
     * @param mixed $value
     */
    public function __invoke($value): bool
    {
        // treat integers as valid floats
        return is_float($value + 0) || is_int($value + 0);
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringIP6.php000064400000000523147361032640017623 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function filter_var;

use const FILTER_FLAG_IPV6;
use const FILTER_VALIDATE_IP;

class StringIP6
{
    public function __invoke(string $value): bool
    {
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/NumberFloat.php000064400000000543147361032640020256 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function is_float;
use function is_int;

class NumberFloat
{
    /**
     * @param mixed $value
     */
    public function __invoke($value): bool
    {
        // treat integers as valid floats
        return is_float($value + 0) || is_int($value + 0);
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringURI.php000064400000000606147361032640017666 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use League\Uri\Exceptions\SyntaxError;
use League\Uri\UriString;

class StringURI
{
    public function __invoke(string $value): bool
    {
        try {
            UriString::parse($value);

            return true;
        } catch (SyntaxError $error) {
            return false;
        }
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringByte.php000064400000000627147361032640020135 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function base64_decode;
use function base64_encode;

class StringByte
{
    /**
     * @param mixed $value
     */
    public function __invoke($value): bool
    {
        //base64-encoded characters, for example, U3dhZ2dlciByb2Nrcw==

        return base64_encode(base64_decode($value, true)) === $value;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringEmail.php000064400000000455147361032640020260 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function filter_var;

use const FILTER_VALIDATE_EMAIL;

class StringEmail
{
    public function __invoke(string $value): bool
    {
        return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringIP4.php000064400000000523147361032640017621 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function filter_var;

use const FILTER_FLAG_IPV4;
use const FILTER_VALIDATE_IP;

class StringIP4
{
    public function __invoke(string $value): bool
    {
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/FormatsContainer.php000064400000004522147361032640021317 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

// Purpose of this class is to allow customizable/extendable list of formats
class FormatsContainer
{
    /** @var mixed[] - array of types->formats->callables */
    private static $list = [
        'string' => [
            'byte'      => StringByte::class,
            'date'      => StringDate::class,
            'date-time' => StringDateTime::class,
            'email'     => StringEmail::class,
            'hostname'  => StringHostname::class,
            'uri'       => StringURI::class,
            'uuid'      => StringUUID::class,
            'ipv4'      => StringIP4::class,
            'ipv6'      => StringIP6::class,
        ],
        'number' => [
            'float'  => NumberFloat::class,
            'double' => NumberDouble::class,
        ],
    ];

    /**
     * Empty the list
     */
    public static function flush(): void
    {
        self::$list = [];
    }

    /**
     * Put default formats (shipped with the package)
     */
    public static function addDefaults(): void
    {
        // string
        self::registerFormat('string', 'byte', StringByte::class);
        self::registerFormat('string', 'date', StringDate::class);
        self::registerFormat('string', 'date-time', StringDateTime::class);
        self::registerFormat('string', 'email', StringEmail::class);
        self::registerFormat('string', 'hostname', StringHostname::class);
        self::registerFormat('string', 'uri', StringURI::class);
        self::registerFormat('string', 'uuid', StringUUID::class);
        self::registerFormat('string', 'ipv4', StringIP4::class);
        self::registerFormat('string', 'ipv6', StringIP6::class);

        // number
        self::registerFormat('string', 'float', NumberFloat::class);
        self::registerFormat('string', 'double', NumberDouble::class);
    }

    /**
     * Add new format to the list
     *
     * @param string|callable $fqcn
     */
    public static function registerFormat(string $type, string $format, $fqcn): void
    {
        self::$list[$type][$format] = $fqcn;
    }

    /**
     * Return FQCN for the format validation class
     *
     * @return string|callable|null
     */
    public static function getFormat(string $type, string $format)
    {
        return self::$list[$type][$format] ?? null;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringDateTime.php000064400000001317147361032640020723 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use DateTime;

class StringDateTime
{
    /**
     * RFC 3339, section 5.6 DateTime format
     * for example, 2017-07-21T17:32:28Z or with optional fractional seconds, 2017-07-21T17:32:28.123Z.
     *
     * @var array<string>
     */
    private static $dateTimeFormats = ['Y-m-d\TH:i:sP', 'Y-m-d H:i:sP', 'Y-m-d\TH:i:s.uP', 'Y-m-d H:i:s.uP'];

    public function __invoke(string $value): bool
    {
        foreach (self::$dateTimeFormats as $dateTimeFormat) {
            if (DateTime::createFromFormat($dateTimeFormat, $value) !== false) {
                return true;
            }
        }

        return false;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringHostname.php000064400000000462147361032640021005 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function filter_var;

use const FILTER_VALIDATE_DOMAIN;

class StringHostname
{
    public function __invoke(string $value): bool
    {
        return filter_var($value, FILTER_VALIDATE_DOMAIN) !== false;
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringUUID.php000064400000000527147361032640017777 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use function preg_match;

class StringUUID
{
    public function __invoke(string $value): bool
    {
        $pattern = '/^[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}$/i';

        return (bool) preg_match($pattern, $value);
    }
}
openapi-psr7-validator/src/Schema/TypeFormats/StringDate.php000064400000000772147361032640020110 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\TypeFormats;

use DateTime;

class StringDate
{
    /**
     * @param mixed $value
     */
    public function __invoke($value): bool
    {
        // full-date notation as defined by RFC 3339, section 5.6, for example, 2017-07-21

        $datetime = DateTime::createFromFormat('Y-m-d', $value);
        if ($datetime === false) {
            return false;
        }

        return $datetime->format('Y-m-d') === $value;
    }
}
openapi-psr7-validator/src/Schema/BreadCrumb.php000064400000002741147361032640015573 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema;

use RuntimeException;

use function array_unshift;
use function is_scalar;
use function sprintf;

// Breadcrumb addresses a value in a complex structure.
// It can address an index in the compound array(object)
class BreadCrumb
{
    /** @var string|null */
    protected $compoundIndex;
    /** @var self|null link to a previous crumb */
    protected $prevCrumb;

    /**
     * @param int|string|null $compoundIndex suitable for array index
     */
    public function __construct($compoundIndex = null)
    {
        if (($compoundIndex !== null) && ! is_scalar($compoundIndex)) {
            throw new RuntimeException(sprintf('BreadCrumb cannot have non-scalar index: %s', $compoundIndex));
        }

        $this->compoundIndex = $compoundIndex;
    }

    /**
     * @param string|int $index
     *
     * @return BreadCrumb
     */
    public function addCrumb($index): self
    {
        $i            = new self($index);
        $i->prevCrumb = $this;

        return $i;
    }

    /**
     * Follow the chain of crumbs to build a full chain of keys
     *
     * @return mixed[] - string/int values are allowed
     */
    public function buildChain(): array
    {
        $keys = [];

        $crumb = $this;
        do {
            array_unshift($keys, $crumb->compoundIndex);
            $crumb = $crumb->prevCrumb;
        } while ($crumb && ($crumb->compoundIndex !== null));

        return $keys;
    }
}
openapi-psr7-validator/src/Schema/Exception/TypeMismatch.php000064400000001175147361032640020132 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use function gettype;
use function sprintf;

// Validation for 'type' keyword failed against a given data
class TypeMismatch extends KeywordMismatch
{
    /**
     * @param mixed $value
     *
     * @return TypeMismatch
     */
    public static function becauseTypeDoesNotMatch(string $expected, $value): self
    {
        $exception          = new self(sprintf("Value expected to be '%s', '%s' given.", $expected, gettype($value)));
        $exception->data    =  $value;
        $exception->keyword = 'type';

        return $exception;
    }
}
openapi-psr7-validator/src/Schema/Exception/SchemaMismatch.php000064400000001536147361032640020412 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use Exception;
use League\OpenAPIValidation\Schema\BreadCrumb;

class SchemaMismatch extends Exception
{
    /** @var BreadCrumb */
    protected $dataBreadCrumb;
    /** @var mixed */
    protected $data;

    public function dataBreadCrumb(): ?BreadCrumb
    {
        return $this->dataBreadCrumb;
    }

    public function hydrateDataBreadCrumb(BreadCrumb $dataBreadCrumb): void
    {
        if ($this->dataBreadCrumb !== null) {
            return;
        }

        $this->dataBreadCrumb = $dataBreadCrumb;
    }

    public function withBreadCrumb(BreadCrumb $breadCrumb): self
    {
        $this->dataBreadCrumb = $breadCrumb;

        return $this;
    }

    /**
     * @return mixed
     */
    public function data()
    {
        return $this->data;
    }
}
openapi-psr7-validator/src/Schema/Exception/FormatMismatch.php000064400000001350147361032640020434 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use function sprintf;

// Indicates that data did not match a given type's "format"
class FormatMismatch extends TypeMismatch
{
    /** @var string */
    protected $format;

    /**
     * @param mixed $value
     *
     * @return FormatMismatch
     */
    public static function fromFormat(string $format, $value, string $type): self
    {
        $i          = new self(sprintf("Value '%s' does not match format %s of type %s", $value, $format, $type));
        $i->format  = $format;
        $i->data    = $value;
        $i->keyword = 'type';

        return $i;
    }

    public function format(): string
    {
        return $this->format;
    }
}
openapi-psr7-validator/src/Schema/Exception/TooManyValidSchemas.php000064400000001616147361032640021375 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use cebe\openapi\spec\Schema;

class TooManyValidSchemas extends KeywordMismatch
{
    /** @var Schema[] */
    protected $validSchemas = [];

    /**
     * @param mixed    $data
     * @param Schema[] $validSchemas
     *
     * @return self
     */
    public static function fromKeywordWithValidSchemas(
        string $keyword,
        $data,
        array $validSchemas,
        ?string $message = null
    ): KeywordMismatch {
        $instance               = new self('Keyword validation failed: ' . $message, 0);
        $instance->keyword      = $keyword;
        $instance->data         = $data;
        $instance->validSchemas = $validSchemas;

        return $instance;
    }

    /**
     * @return Schema[]
     */
    public function validSchemas(): array
    {
        return $this->validSchemas;
    }
}
openapi-psr7-validator/src/Schema/Exception/KeywordMismatch.php000064400000001360147361032640020631 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use Throwable;

// Indicates that data was not matched against a schema's keyword
class KeywordMismatch extends SchemaMismatch
{
    /** @var string */
    protected $keyword;

    /**
     * @param mixed $data
     *
     * @return KeywordMismatch
     */
    public static function fromKeyword(string $keyword, $data, ?string $message = null, ?Throwable $prev = null): self
    {
        $instance          = new self('Keyword validation failed: ' . $message, 0, $prev);
        $instance->keyword = $keyword;
        $instance->data    = $data;

        return $instance;
    }

    public function keyword(): string
    {
        return $this->keyword;
    }
}
openapi-psr7-validator/src/Schema/Exception/InvalidSchema.php000064400000002120147361032640020221 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use RuntimeException;
use Throwable;

use function sprintf;

// Something wrong with the OpenAPI schema. This sort of errors should have been caught by cebe's underlying package.
final class InvalidSchema extends RuntimeException
{
    /**
     * This exception can be thrown if unexpected schema values, or data values are detected
     * for example, 'minLength' keyword expects data to be 'string', if something else given - this exception raises
     *
     * @return InvalidSchema
     */
    public static function becauseDefensiveSchemaValidationFailed(Throwable $e): self
    {
        return new self('Schema(or data) validation failed: ' . $e->getMessage(), $e->getCode(), $e);
    }

    public static function becauseTypeIsNotKnown(string $type): self
    {
        return new self(sprintf("Type '%s' is unexpected.", $type));
    }

    public static function becauseBracesAreNotBalanced(string $path): self
    {
        return new self(sprintf("Braces in path '%s' are not balanced.", $path));
    }
}
openapi-psr7-validator/src/Schema/Exception/NotEnoughValidSchemas.php000064400000001656147361032640021721 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use Throwable;

class NotEnoughValidSchemas extends KeywordMismatch
{
    /** @var Throwable[] */
    protected $innerExceptions = [];

    /**
     * @param mixed       $data
     * @param Throwable[] $innerExceptions
     *
     * @return self
     */
    public static function fromKeywordWithInnerExceptions(
        string $keyword,
        $data,
        array $innerExceptions,
        ?string $message = null
    ): KeywordMismatch {
        $instance                  = new self('Keyword validation failed: ' . $message, 0);
        $instance->keyword         = $keyword;
        $instance->data            = $data;
        $instance->innerExceptions = $innerExceptions;

        return $instance;
    }

    /**
     * @return Throwable[]
     */
    public function innerExceptions(): array
    {
        return $this->innerExceptions;
    }
}
openapi-psr7-validator/src/Schema/Exception/ContentTypeMismatch.php000064400000000764147361032640021470 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Exception;

use function sprintf;

// Indicates that data did not match a given content-type
final class ContentTypeMismatch extends SchemaMismatch
{
    public static function fromContentType(string $contentType, string $value): self
    {
        $exception       = new self(sprintf("Value '%s' does not match content-type %s", $value, $contentType));
        $exception->data = $value;

        return $exception;
    }
}
openapi-psr7-validator/src/Schema/Keywords/MinLength.php000064400000003222147361032640017254 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function mb_strlen;
use function sprintf;

class MinLength extends BaseKeyword
{
    /**
     * A string instance is valid against this keyword if its length is
     * greater than, or equal to, the value of this keyword.
     *
     * The length of a string instance is defined as the number of its
     * characters as defined by RFC 7159 [RFC7159].
     *
     * The value of this keyword MUST be an integer.  This integer MUST be
     * greater than, or equal to, 0.
     *
     * "minLength", if absent, may be considered as being present with
     * integer value 0.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     * @throws InvalidSchema
     */
    public function validate($data, int $minLength): void
    {
        try {
            Validator::stringType()->assert($data);
            Validator::intVal()->assert($minLength);
            Validator::trueVal()->assert($minLength >= 0);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (mb_strlen($data) < $minLength) {
            throw KeywordMismatch::fromKeyword(
                'minLength',
                $data,
                sprintf("Length of '%s' must be longer or equal to %d", $data, $minLength)
            );
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Nullable.php000064400000001101147361032640017117 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;

class Nullable extends BaseKeyword
{
    /**
     * Allows sending a null value for the defined schema. Default value is false.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, bool $nullable): void
    {
        if (! $nullable && ($data === null)) {
            throw KeywordMismatch::fromKeyword('nullable', $data, 'Value cannot be null');
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/MinProperties.php000064400000002753147361032640020177 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function count;
use function sprintf;

class MinProperties extends BaseKeyword
{
    /**
     * The value of this keyword MUST be an integer.  This integer MUST be
     * greater than, or equal to, 0.
     *
     * An object instance is valid against "minProperties" if its number of
     * properties is greater than, or equal to, the value of this keyword.
     *
     * If this keyword is not present, it may be considered present with a
     * value of 0.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, int $minProperties): void
    {
        try {
            Validator::arrayType()->assert($data);
            Validator::trueVal()->assert($minProperties >= 0);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (count($data) < $minProperties) {
            throw KeywordMismatch::fromKeyword(
                'minProperties',
                $data,
                sprintf("The number of object's properties must be greater or equal to %d", $minProperties)
            );
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Enum.php000064400000002625147361032640016301 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function count;
use function in_array;

class Enum extends BaseKeyword
{
    /**
     * The value of this keyword MUST be an array.  This array SHOULD have
     * at least one element.  Elements in the array SHOULD be unique.
     *
     * Elements in the array MAY be of any type, including null.
     *
     * An instance validates successfully against this keyword if its value
     * is equal to one of the elements in this keyword's array value.
     *
     * @param mixed   $data
     * @param mixed[] $enum - can be strings or numbers
     *
     * @throws KeywordMismatch
     */
    public function validate($data, array $enum): void
    {
        try {
            Validator::arrayType()->assert($enum);
            Validator::trueVal()->assert(count($enum) >= 1);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (! in_array($data, $enum, true)) {
            throw KeywordMismatch::fromKeyword('enum', $data, 'Value must be present in the enum');
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/MaxProperties.php000064400000002600147361032640020170 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function count;
use function sprintf;

class MaxProperties extends BaseKeyword
{
    /**
     * The value of this keyword MUST be an integer.  This integer MUST be
     * greater than, or equal to, 0.
     *
     * An object instance is valid against "maxProperties" if its number of
     * properties is less than, or equal to, the value of this keyword.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, int $maxProperties): void
    {
        try {
            Validator::arrayType()->assert($data);
            Validator::trueVal()->assert($maxProperties >= 0);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (count($data) > $maxProperties) {
            throw KeywordMismatch::fromKeyword(
                'maxProperties',
                $data,
                sprintf("The number of object's properties must be less or equal to %d", $maxProperties)
            );
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Minimum.php000064400000004662147361032640017013 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Rules\NumericVal;
use Respect\Validation\Validator;

use function class_exists;
use function sprintf;

class Minimum extends BaseKeyword
{
    /**
     * The value of "minimum" MUST be a number, representing a lower limit
     * for a numeric instance.
     *
     * If the instance is a number, then this keyword validates if
     * "exclusiveMinimum" is true and instance is greater than the provided
     * value, or else if the instance is greater than or exactly equal to
     * the provided value.
     *
     * The value of "exclusiveMinimum" MUST be a boolean, representing
     * whether the limit in "minimum" is exclusive or not.  An undefined
     * value is the same as false.
     *
     * If "exclusiveMinimum" is true, then a numeric instance SHOULD NOT be
     * equal to the value specified in "minimum".  If "exclusiveMinimum" is
     * false (or not specified), then a numeric instance MAY be equal to the
     * value of "minimum".
     *
     * @param mixed     $data
     * @param int|float $minimum
     *
     * @throws KeywordMismatch
     */
    public function validate($data, $minimum, bool $exclusiveMinimum = false): void
    {
        try {
            if (class_exists(NumericVal::class)) {
                Validator::numericVal()->assert($data);
                Validator::numericVal()->assert($minimum);
            } else {
                Validator::numeric()->assert($data);
                Validator::numeric()->assert($minimum);
            }
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if ($exclusiveMinimum && $data <= $minimum) {
            throw KeywordMismatch::fromKeyword(
                'minimum',
                $data,
                sprintf('Value %d must be greater than %d', $data, $minimum)
            );
        }

        if (! $exclusiveMinimum && $data < $minimum) {
            throw KeywordMismatch::fromKeyword(
                'minimum',
                $data,
                sprintf('Value %d must be greater or equal to %d', $data, $minimum)
            );
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Properties.php000064400000010311147361032640017520 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function array_diff;
use function array_key_exists;
use function array_keys;
use function implode;
use function sprintf;

class Properties extends BaseKeyword
{
    /** @var int this can be Validator::VALIDATE_AS_REQUEST or Validator::VALIDATE_AS_RESPONSE */
    protected $validationDataType;
    /** @var BreadCrumb */
    protected $dataBreadCrumb;

    public function __construct(CebeSchema $parentSchema, int $type, BreadCrumb $breadCrumb)
    {
        parent::__construct($parentSchema);
        $this->validationDataType = $type;
        $this->dataBreadCrumb     = $breadCrumb;
    }

    /**
     * Property definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced).
     * If absent, it can be considered the same as an empty object.
     *
     *
     * Value can be boolean or object.
     * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
     * Consistent with JSON Schema, additionalProperties defaults to true.
     *
     * The value of "additionalProperties" MUST be a boolean or a schema.
     *
     * If "additionalProperties" is absent, it may be considered present
     * with an empty schema as a value.
     *
     * If "additionalProperties" is true, validation always succeeds.
     *
     * If "additionalProperties" is false, validation succeeds only if the
     * instance is an object and all properties on the instance were covered
     * by "properties" and/or "patternProperties".
     *
     * If "additionalProperties" is an object, validate the value as a
     * schema to all of the properties that weren't validated by
     * "properties" nor "patternProperties".
     *
     * @param mixed        $data
     * @param CebeSchema[] $properties
     * @param mixed        $additionalProperties
     *
     * @throws SchemaMismatch
     */
    public function validate($data, array $properties, $additionalProperties): void
    {
        try {
            Validator::arrayType()->assert($data);
            Validator::arrayVal()->assert($properties);
            Validator::each(Validator::instance(CebeSchema::class))->assert($properties);
        } catch (Exception | ExceptionInterface $exception) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($exception);
        }

        $schemaValidator = new SchemaValidator($this->validationDataType);

        // Validate against "properties"
        foreach ($properties as $propName => $propSchema) {
            if (! array_key_exists($propName, $data)) {
                continue;
            }

            $schemaValidator->validate($data[$propName], $propSchema, $this->dataBreadCrumb->addCrumb($propName));
        }

        // Validate the rest against "additionalProperties"
        if (! ($additionalProperties instanceof CebeSchema)) {
            // are there unexpected properties?
            $unexpectedProps = array_diff(array_keys($data), array_keys($properties));

            if ($unexpectedProps && $additionalProperties === false) {
                throw KeywordMismatch::fromKeyword(
                    'additionalProperties',
                    $data,
                    sprintf('Data has additional properties (%s) which are not allowed', implode(',', $unexpectedProps))
                );
            }

            return;
        }

        foreach ($data as $propName => $propSchema) {
            if (isset($properties[$propName])) {
                continue;
            }

            // if not covered by "properties"
            $schemaValidator->validate(
                $data[$propName],
                $additionalProperties,
                $this->dataBreadCrumb->addCrumb($propName)
            );
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Discriminator.php000064400000000362147361032640020200 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

class Discriminator extends BaseKeyword
{
    /**
     * @param mixed $data
     */
    public function validate($data): void
    {
        // TODO
    }
}
openapi-psr7-validator/src/Schema/Keywords/Type.php000064400000006404147361032640016315 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Type as CebeType;
use League\OpenAPIValidation\Foundation\ArrayHelper;
use League\OpenAPIValidation\Schema\Exception\FormatMismatch;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\TypeMismatch;
use League\OpenAPIValidation\Schema\TypeFormats\FormatsContainer;
use RuntimeException;

use function class_exists;
use function is_array;
use function is_bool;
use function is_int;
use function is_numeric;
use function is_object;
use function is_string;
use function sprintf;

class Type extends BaseKeyword
{
    /**
     * The value of this keyword MUST be either a string ONLY.
     *
     * String values MUST be one of the seven primitive types defined by the
     * core specification.
     *
     * An instance matches successfully if its primitive type is one of the
     * types defined by keyword.  Recall: "number" includes "integer".
     *
     * @param mixed $data
     *
     * @throws TypeMismatch
     */
    public function validate($data, string $type, ?string $format = null): void
    {
        switch ($type) {
            case CebeType::OBJECT:
                if (! is_object($data) && ! (is_array($data) && ArrayHelper::isAssoc($data)) && $data !== []) {
                    throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::OBJECT, $data);
                }

                break;
            case CebeType::ARRAY:
                if (! is_array($data) || ArrayHelper::isAssoc($data)) {
                    throw TypeMismatch::becauseTypeDoesNotMatch('array', $data);
                }

                break;
            case CebeType::BOOLEAN:
                if (! is_bool($data)) {
                    throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::BOOLEAN, $data);
                }

                break;
            case CebeType::NUMBER:
                if (is_string($data) || ! is_numeric($data)) {
                    throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::NUMBER, $data);
                }

                break;
            case CebeType::INTEGER:
                if (! is_int($data)) {
                    throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::INTEGER, $data);
                }

                break;
            case CebeType::STRING:
                if (! is_string($data)) {
                    throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::STRING, $data);
                }

                break;
            default:
                throw InvalidSchema::becauseTypeIsNotKnown($type);
        }

        // 2. Validate format now

        if (! $format) {
            return;
        }

        $formatValidator = FormatsContainer::getFormat($type, $format); // callable or FQCN
        if ($formatValidator === null) {
            return;
        }

        if (is_string($formatValidator) && ! class_exists($formatValidator)) {
            throw new RuntimeException(sprintf("'%s' does not loaded", $formatValidator));
        }

        if (is_string($formatValidator)) {
            $formatValidator = new $formatValidator();
        }

        if (! $formatValidator($data)) {
            throw FormatMismatch::fromFormat($format, $data, $type);
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/MultipleOf.php000064400000003055147361032640017453 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Rules\NumericVal;
use Respect\Validation\Validator;

use function class_exists;
use function sprintf;

class MultipleOf extends BaseKeyword
{
    /**
     * The value of "multipleOf" MUST be a number, strictly greater than 0.
     * A numeric instance is only valid if division by this keyword's value results in an integer.
     *
     * @param mixed     $data
     * @param int|float $multipleOf
     *
     * @throws KeywordMismatch
     */
    public function validate($data, $multipleOf): void
    {
        try {
            if (class_exists(NumericVal::class)) {
                Validator::numericVal()->assert($data);
                Validator::numericVal()->positive()->assert($multipleOf);
            } else {
                Validator::numeric()->assert($data);
                Validator::numeric()->positive()->assert($multipleOf);
            }
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        $value = (float) ($data / $multipleOf);
        if ($value - (int) $value !== 0.0) {
            throw KeywordMismatch::fromKeyword('multipleOf', $data, sprintf('Division by %d did not resulted in integer', $multipleOf));
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/AnyOf.php000064400000004745147361032640016416 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use League\OpenAPIValidation\Schema\Exception\NotEnoughValidSchemas;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

class AnyOf extends BaseKeyword
{
    /** @var int this can be Validator::VALIDATE_AS_REQUEST or Validator::VALIDATE_AS_RESPONSE */
    protected $validationDataType;
    /** @var BreadCrumb */
    protected $dataBreadCrumb;

    public function __construct(CebeSchema $parentSchema, int $type, BreadCrumb $breadCrumb)
    {
        parent::__construct($parentSchema);
        $this->validationDataType = $type;
        $this->dataBreadCrumb     = $breadCrumb;
    }

    /**
     * This keyword's value MUST be an array.  This array MUST have at least
     * one element.
     *
     * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
     *
     * An instance validates successfully against this keyword if it
     * validates successfully against at least one schema defined by this
     * keyword's value.
     *
     * @param mixed        $data
     * @param CebeSchema[] $anyOf
     *
     * @throws KeywordMismatch
     */
    public function validate($data, array $anyOf): void
    {
        try {
            Validator::arrayVal()->assert($anyOf);
            Validator::each(Validator::instance(CebeSchema::class))->assert($anyOf);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        $innerExceptions = [];

        foreach ($anyOf as $schema) {
            $schemaValidator = new SchemaValidator($this->validationDataType);
            try {
                $schemaValidator->validate($data, $schema, $this->dataBreadCrumb);

                return;
            } catch (SchemaMismatch $e) {
                $innerExceptions[] = $e;
            }
        }

        throw NotEnoughValidSchemas::fromKeywordWithInnerExceptions(
            'anyOf',
            $data,
            $innerExceptions,
            'Data must match at least one schema'
        );
    }
}
openapi-psr7-validator/src/Schema/Keywords/Maximum.php000064400000004647147361032640017020 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Rules\NumericVal;
use Respect\Validation\Validator;

use function class_exists;
use function sprintf;

class Maximum extends BaseKeyword
{
    /**
     * The value of "maximum" MUST be a number, representing an upper limit
     * for a numeric instance.
     *
     * If the instance is a number, then this keyword validates if
     * "exclusiveMaximum" is true and instance is less than the provided
     * value, or else if the instance is less than or exactly equal to the
     * provided value.
     *
     * The value of "exclusiveMaximum" MUST be a boolean, representing
     * whether the limit in "maximum" is exclusive or not.  An undefined
     * value is the same as false.
     *
     * If "exclusiveMaximum" is true, then a numeric instance SHOULD NOT be
     * equal to the value specified in "maximum".  If "exclusiveMaximum" is
     * false (or not specified), then a numeric instance MAY be equal to the
     * value of "maximum".
     *
     * @param mixed     $data
     * @param int|float $maximum
     *
     * @throws KeywordMismatch
     */
    public function validate($data, $maximum, bool $exclusiveMaximum = false): void
    {
        try {
            if (class_exists(NumericVal::class)) {
                Validator::numericVal()->assert($data);
                Validator::numericVal()->assert($maximum);
            } else {
                Validator::numeric()->assert($data);
                Validator::numeric()->assert($maximum);
            }
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if ($exclusiveMaximum && $data >= $maximum) {
            throw KeywordMismatch::fromKeyword(
                'maximum',
                $data,
                sprintf('Value %d must be less than %d', $data, $maximum)
            );
        }

        if (! $exclusiveMaximum && $data > $maximum) {
            throw KeywordMismatch::fromKeyword(
                'maximum',
                $data,
                sprintf('Value %d must be less or equal to %d', $data, $maximum)
            );
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/MinItems.php000064400000002634147361032640017122 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function count;
use function sprintf;

class MinItems extends BaseKeyword
{
    /**
     * The value of this keyword MUST be an integer.  This integer MUST be
     * greater than, or equal to, 0.
     *
     * An array instance is valid against "minItems" if its size is greater
     * than, or equal to, the value of this keyword.
     *
     * If this keyword is not present, it may be considered present with a
     * value of 0.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, int $minItems): void
    {
        try {
            Validator::arrayType()->assert($data);
            Validator::intVal()->assert($minItems);
            Validator::trueVal()->assert($minItems >= 0);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (count($data) < $minItems) {
            throw KeywordMismatch::fromKeyword('minItems', $data, sprintf('Size of an array must be greater or equal to %d', $minItems));
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/AdditionalProperties.php000064400000001032147361032640021511 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

class AdditionalProperties extends BaseKeyword
{
    /**
     * @param mixed $data
     */
    public function validate($data, bool $additionalProperties): void
    {
        // The additionalProperties keyword specifies the type of values in the dictionary.
        // Values can be primitives (strings, numbers or boolean values), arrays or objects.

        // Not used. See \OpenAPIValidation\Schema\Keywords\Properties::validate for usage
    }
}
openapi-psr7-validator/src/Schema/Keywords/OneOf.php000064400000006072147361032640016403 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use League\OpenAPIValidation\Schema\Exception\NotEnoughValidSchemas;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\Exception\TooManyValidSchemas;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function count;
use function sprintf;

class OneOf extends BaseKeyword
{
    /** @var int this can be Validator::VALIDATE_AS_REQUEST or Validator::VALIDATE_AS_RESPONSE */
    protected $validationDataType;
    /** @var BreadCrumb */
    protected $dataBreadCrumb;

    public function __construct(CebeSchema $parentSchema, int $type, BreadCrumb $breadCrumb)
    {
        parent::__construct($parentSchema);
        $this->validationDataType = $type;
        $this->dataBreadCrumb     = $breadCrumb;
    }

    /**
     * This keyword's value MUST be an array.  This array MUST have at least
     * one element.
     *
     * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
     *
     * An instance validates successfully against this keyword if it
     * validates successfully against exactly one schema defined by this
     * keyword's value.
     *
     * @param mixed        $data
     * @param CebeSchema[] $oneOf
     *
     * @throws KeywordMismatch
     */
    public function validate($data, array $oneOf): void
    {
        try {
            Validator::arrayVal()->assert($oneOf);
            Validator::each(Validator::instance(CebeSchema::class))->assert($oneOf);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        // Validate against all schemas
        $schemaValidator = new SchemaValidator($this->validationDataType);
        $innerExceptions = [];
        $validSchemas    = [];

        foreach ($oneOf as $schema) {
            try {
                $schemaValidator->validate($data, $schema, $this->dataBreadCrumb);
                $validSchemas[] = $schema;
            } catch (SchemaMismatch $e) {
                $innerExceptions[] = $e;
            }
        }

        if (count($validSchemas) === 1) {
            return;
        }

        if (count($validSchemas) < 1) {
            throw NotEnoughValidSchemas::fromKeywordWithInnerExceptions(
                'oneOf',
                $data,
                $innerExceptions,
                'Data must match exactly one schema, but matched none'
            );
        }

        throw TooManyValidSchemas::fromKeywordWithValidSchemas(
            'oneOf',
            $data,
            $validSchemas,
            sprintf('Data must match exactly one schema, but matched %d', count($validSchemas))
        );
    }
}
openapi-psr7-validator/src/Schema/Keywords/UniqueItems.php000064400000003147147361032640017645 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function array_map;
use function array_unique;
use function count;
use function var_export;

class UniqueItems extends BaseKeyword
{
    /**
     * The value of this keyword MUST be a boolean.
     *
     * If this keyword has boolean value false, the instance validates
     * successfully.  If it has boolean value true, the instance validates
     * successfully if all of its elements are unique.
     *
     * If not present, this keyword may be considered present with boolean
     * value false.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, bool $uniqueItems): void
    {
        if (! $uniqueItems) {
            return;
        }

        try {
            Validator::arrayType()->assert($data);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        $items = $data;
        if (count($data)) {
            $items = array_map(static function ($item) {
                return var_export($item, true);
            }, $data);
        }

        if (count(array_unique($items)) !== count($items)) {
            throw KeywordMismatch::fromKeyword('uniqueItems', $data, 'All array items must be unique');
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Not.php000064400000003733147361032640016136 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

class Not extends BaseKeyword
{
    /** @var int this can be Validator::VALIDATE_AS_REQUEST or Validator::VALIDATE_AS_RESPONSE */
    protected $validationDataType;
    /** @var BreadCrumb */
    protected $dataBreadCrumb;

    public function __construct(CebeSchema $parentSchema, int $type, BreadCrumb $breadCrumb)
    {
        parent::__construct($parentSchema);
        $this->validationDataType = $type;
        $this->dataBreadCrumb     = $breadCrumb;
    }

    /**
     * This keyword's value MUST be an object.
     * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
     *
     * An instance is valid against this keyword if it fails to validate
     * successfully against the schema defined by this keyword.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, CebeSchema $not): void
    {
        try {
            Validator::instance(CebeSchema::class)->assert($not);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        $schemaValidator = new SchemaValidator($this->validationDataType);

        try {
            $schemaValidator->validate($data, $not, $this->dataBreadCrumb);
        } catch (SchemaMismatch $e) {
            return;
        }

        throw KeywordMismatch::fromKeyword('not', $data, 'Data must not match the schema');
    }
}
openapi-psr7-validator/src/Schema/Keywords/Pattern.php000064400000002712147361032640017007 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function preg_match;
use function sprintf;
use function str_replace;

class Pattern extends BaseKeyword
{
    /**
     * The value of this keyword MUST be a string.  This string SHOULD be a
     * valid regular expression, according to the ECMA 262 regular
     * expression dialect.
     *
     * A string instance is considered valid if the regular expression
     * matches the instance successfully.  Recall: regular expressions are
     * not implicitly anchored.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, string $pattern): void
    {
        try {
            Validator::stringType()->assert($data);
            Validator::stringType()->assert($pattern);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        $pattern = sprintf('#%s#u', str_replace('#', '\#', $pattern));

        if (! preg_match($pattern, $data)) {
            throw KeywordMismatch::fromKeyword('pattern', $data, sprintf('Data does not match pattern \'%s\'', $pattern));
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/MaxItems.php000064400000002557147361032640017130 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function count;
use function sprintf;

class MaxItems extends BaseKeyword
{
    /**
     * The value of this keyword MUST be an integer.  This integer MUST be
     * greater than, or equal to, 0.
     *
     * An array instance is valid against "maxItems" if its size is less
     * than, or equal to, the value of this keyword.
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, int $maxItems): void
    {
        try {
            Validator::arrayType()->assert($data);
            Validator::intVal()->assert($maxItems);
            Validator::trueVal()->assert($maxItems >= 0);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (count($data) > $maxItems) {
            throw KeywordMismatch::fromKeyword(
                'maxItems',
                $data,
                sprintf('Size of an array must be less or equal to %d', $maxItems)
            );
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/MaxLength.php000064400000003030147361032640017253 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function mb_strlen;
use function sprintf;

class MaxLength extends BaseKeyword
{
    /**
     * The value of this keyword MUST be a non-negative integer.
     *
     * The value of this keyword MUST be an integer.  This integer MUST be
     * greater than, or equal to, 0.
     *
     * A string instance is valid against this keyword if its length is less
     * than, or equal to, the value of this keyword.
     *
     * The length of a string instance is defined as the number of its
     * characters as defined by RFC 7159 [RFC7159].
     *
     * @param mixed $data
     *
     * @throws KeywordMismatch
     */
    public function validate($data, int $maxLength): void
    {
        try {
            Validator::stringType()->assert($data);
            Validator::intType()->assert($maxLength);
            Validator::trueVal()->assert($maxLength >= 0);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (mb_strlen($data) > $maxLength) {
            throw KeywordMismatch::fromKeyword('maxLength', $data, sprintf("Length of '%s' must be shorter or equal to %d", $data, $maxLength));
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Required.php000064400000006603147361032640017155 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function array_unique;
use function count;
use function sprintf;

class Required extends BaseKeyword
{
    /** @var int this can be Validator::VALIDATE_AS_REQUEST or Validator::VALIDATE_AS_RESPONSE */
    protected $validationDataType;
    /** @var BreadCrumb */
    private $breadCrumb;

    public function __construct(CebeSchema $parentSchema, int $type, BreadCrumb $breadCrumb)
    {
        parent::__construct($parentSchema);
        $this->validationDataType = $type;
        $this->breadCrumb         = $breadCrumb;
    }

    /**
     * The value of this keyword MUST be an array.  This array MUST have at
     * least one element.  Elements of this array MUST be strings, and MUST
     * be unique.
     *
     * An object instance is valid against this keyword if its property set
     * contains all elements in this keyword's array value.
     *
     * If a readOnly or writeOnly property is included in the required list, required affects just the relevant scope – responses only or requests only.
     * That is, read-only required properties apply to responses only, and write-only required properties – to requests only.
     *
     * @param mixed    $data
     * @param string[] $required
     *
     * @throws KeywordMismatch
     */
    public function validate($data, array $required): void
    {
        try {
            Validator::arrayType()->assert($data);
            Validator::arrayType()->assert($required);
            Validator::each(Validator::stringType())->assert($required);
            Validator::trueVal()->assert(count(array_unique($required)) === count($required));
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        foreach ($required as $reqProperty) {
            $propertyFound = false;
            foreach ($data as $property => $value) {
                $propertyFound = $propertyFound || ($reqProperty === $property);
            }

            if (! $propertyFound) {
                // respect writeOnly/readOnly keywords
                if (
                    (
                        ($this->parentSchema->properties[$reqProperty]->writeOnly ?? false) &&
                        $this->validationDataType === SchemaValidator::VALIDATE_AS_RESPONSE
                    )
                    ||
                    (
                        ($this->parentSchema->properties[$reqProperty]->readOnly ?? false) &&
                        $this->validationDataType === SchemaValidator::VALIDATE_AS_REQUEST
                    )
                ) {
                    continue;
                }

                throw KeywordMismatch::fromKeyword(
                    'required',
                    $data,
                    sprintf("Required property '%s' must be present in the object", $reqProperty)
                )->withBreadCrumb($this->breadCrumb->addCrumb($reqProperty));
            }
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/AllOf.php000064400000004031147361032640016363 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

final class AllOf extends BaseKeyword
{
    /** @var int this can be Validator::VALIDATE_AS_REQUEST or Validator::VALIDATE_AS_RESPONSE */
    protected $validationDataType;
    /** @var BreadCrumb */
    protected $dataBreadCrumb;

    public function __construct(CebeSchema $parentSchema, int $type, BreadCrumb $breadCrumb)
    {
        parent::__construct($parentSchema);
        $this->validationDataType = $type;
        $this->dataBreadCrumb     = $breadCrumb;
    }

    /**
     * This keyword's value MUST be an array.  This array MUST have at least
     * one element.
     *
     * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
     *
     * An instance validates successfully against this keyword if it
     * validates successfully against all schemas defined by this keyword's
     * value.
     *
     * @param mixed        $data
     * @param CebeSchema[] $allOf
     *
     * @throws SchemaMismatch
     */
    public function validate($data, array $allOf): void
    {
        try {
            Validator::arrayVal()->assert($allOf);
            Validator::each(Validator::instance(CebeSchema::class))->assert($allOf);
        } catch (Exception | ExceptionInterface $exception) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($exception);
        }

        // Validate against all schemas
        $schemaValidator = new SchemaValidator($this->validationDataType);
        foreach ($allOf as $schema) {
            $schemaValidator->validate($data, $schema, $this->dataBreadCrumb);
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/Items.php000064400000004020147361032640016445 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;
use League\OpenAPIValidation\Schema\BreadCrumb;
use League\OpenAPIValidation\Schema\Exception\InvalidSchema;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use League\OpenAPIValidation\Schema\SchemaValidator;
use Respect\Validation\Exceptions\Exception;
use Respect\Validation\Exceptions\ExceptionInterface;
use Respect\Validation\Validator;

use function sprintf;

class Items extends BaseKeyword
{
    /** @var int this can be Validator::VALIDATE_AS_REQUEST or Validator::VALIDATE_AS_RESPONSE */
    protected $validationDataType;
    /** @var BreadCrumb */
    protected $dataBreadCrumb;

    public function __construct(CebeSchema $parentSchema, int $type, BreadCrumb $breadCrumb)
    {
        parent::__construct($parentSchema);
        $this->validationDataType = $type;
        $this->dataBreadCrumb     = $breadCrumb;
    }

    /**
     * Value MUST be an object and not an array.
     * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
     * items MUST be present if the type is array.
     *
     * @param mixed $data
     *
     * @throws SchemaMismatch
     */
    public function validate($data, CebeSchema $itemsSchema): void
    {
        try {
            Validator::arrayVal()->assert($data);
            Validator::instance(CebeSchema::class)->assert($itemsSchema);
        } catch (Exception | ExceptionInterface $e) {
            throw InvalidSchema::becauseDefensiveSchemaValidationFailed($e);
        }

        if (! isset($this->parentSchema->type) || ($this->parentSchema->type !== 'array')) {
            throw new InvalidSchema(sprintf('items MUST be present if the type is array'));
        }

        $schemaValidator = new SchemaValidator($this->validationDataType);
        foreach ($data as $dataIndex => $dataItem) {
            $schemaValidator->validate($dataItem, $itemsSchema, $this->dataBreadCrumb->addCrumb($dataIndex));
        }
    }
}
openapi-psr7-validator/src/Schema/Keywords/BaseKeyword.php000064400000000516147361032640017611 0ustar00<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Schema\Keywords;

use cebe\openapi\spec\Schema as CebeSchema;

abstract class BaseKeyword
{
    /** @var CebeSchema */
    protected $parentSchema;

    public function __construct(CebeSchema $parentSchema)
    {
        $this->parentSchema = $parentSchema;
    }
}
openapi-psr7-validator/README.md000064400000035362147361032640012351 0ustar00[![Latest Stable Version](https://poser.pugx.org/league/openapi-psr7-validator/v/stable)](https://packagist.org/packages/league/openapi-psr7-validator)
[![Build Status](https://travis-ci.org/thephpleague/openapi-psr7-validator.svg?branch=master)](https://travis-ci.org/thephpleague/openapi-psr7-validator)
[![License](https://poser.pugx.org/league/openapi-psr7-validator/license)](https://packagist.org/packages/lezhnev74/openapi-psr7-validator)
![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)

# OpenAPI PSR-7 Message (HTTP Request/Response) Validator
This package can validate PSR-7 messages against OpenAPI (3.0.x) specifications 
expressed in YAML or JSON. 

![](image.jpg)

## Installation
```
composer require league/openapi-psr7-validator
```

## OpenAPI (OAS) Terms
There are some specific terms that are used in the package. These terms come 
from OpenAPI:
- `specification` - an OpenAPI document describing an API, expressed in JSON or YAML file
- `data` - actual thing that we validate against a specification, including body and metadata
- `schema` - the part of the specification that describes the body of the request / response
- `keyword` - properties that are used to describe the instance are called key
words, or schema keywords
- `path` - a relative path to an individual endpoint
- `operation` - a method that we apply on the path (like `get /password`)
- `response` - described response (includes status code, content types etc)


## How To Validate

### ServerRequest Message
You can validate `\Psr\Http\Message\ServerRequestInterface` instance like this:

```php
$yamlFile = "api.yaml";
$jsonFile = "api.json";

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getServerRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getServerRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getServerRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getServerRequestValidator();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getServerRequestValidator();

$match = $validator->validate($request);
```

As a result you would get and `OperationAddress $match` which has matched the given request. If you already know
the operation which should match your request (i.e you have routing in your project), you can use 
`RouterRequestValidator`

```php
$address = new \League\OpenAPIValidation\PSR7\OperationAddress('/some/operation', 'post');

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getRoutedRequestValidator();

$validator->validate($address, $request);
```

This would simplify validation a lot and give you more performance.

### Request Message
You can validate `\Psr\Http\Message\RequestInterface` instance like this:

```php
$yamlFile = "api.yaml";
$jsonFile = "api.json";

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getRequestValidator();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getRequestValidator();

$match = $validator->validate($request);
```

### Response Message
Validation of `\Psr\Http\Message\ResponseInterface` is a bit more complicated
. Because you need not only YAML file and Response itself, but also you need 
to know which operation this response belongs to (in terms of OpenAPI).

Example:

```php
$yamlFile = "api.yaml";
$jsonFile = "api.json";

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getResponseValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getResponseValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getResponseValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getResponseValidator();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getResponseValidator();

$operation = new \League\OpenAPIValidation\PSR7\OperationAddress('/password/gen', 'get') ;

$validator->validate($operation, $response);
```

### Reuse Schema After Validation

`\League\OpenAPIValidation\PSR7\ValidatorBuilder` reads and compiles schema in memory as instance of `\cebe\openapi\spec\OpenApi`. Validators use this instance to perform validation logic. You can reuse this instance after the validation like this:

```php
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getServerRequestValidator();
# or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getResponseValidator();

/** @var \cebe\openapi\spec\OpenApi */
$openApi = $validator->getSchema();
```

### PSR-15 Middleware
PSR-15 middleware can be used like this:

```php
$yamlFile = 'api.yaml';
$jsonFile = 'api.json';

$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYamlFile($yamlFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYaml(file_get_contents($yamlFile))->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJsonFile($jsonFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJson(file_get_contents($jsonFile))->getValidationMiddleware();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidationMiddlewareBuilder)->fromSchema($schema)->getValidationMiddleware();
```

### SlimFramework Middleware
Slim framework uses slightly different middleware interface, so here is an 
adapter which you can use like this:

```php
$yamlFile = 'api.yaml';
$jsonFile = 'api.json';

$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYamlFile($yamlFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYaml(file_get_contents($yamlFile))->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJsonFile($jsonFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJson(file_get_contents($jsonFile))->getValidationMiddleware();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidationMiddlewareBuilder)->fromSchema($schema)->getValidationMiddleware();

$slimMiddleware = new \League\OpenAPIValidation\PSR15\SlimAdapter($psr15Middleware);

/** @var \Slim\App $app */
$app->add($slimMiddleware);
```

### Caching Layer / PSR-6 Support
PSR-7 Validator has a built-in caching layer (based on [PSR-6](https://www.php-fig.org/psr/psr-6/) interfaces) which saves time on parsing OpenAPI specs. It is optional.
You enable caching if you pass a configured Cache Pool Object to the static constructor like this:
```php
// Configure a PSR-6 Cache Pool
$cachePool = new ArrayCachePool();

// Pass it as a 2nd argument
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)
    ->fromYamlFile($yamlFile)
    ->setCache($cachePool)
    ->getResponseValidator();
# or
$psr15Middleware = (new \OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)
    ->fromYamlFile($yamlFile)
    ->setCache($cachePool)
    ->getValidationMiddleware();
```

You can use `->setCache($pool, $ttl)` call for both PSR-7 and PSR-15 builder in order to set 
[proper expiration ttl in seconds (or explicit `null`)](https://www.php-fig.org/psr/psr-6/#definitions)

If you want take control over the cache key for schema item, or your cache does not support cache key generation by itself
you can `->overrideCacheKey('my_custom_key')` to ensure cache uses key you want.

### Standalone OpenAPI Validator
The package contains a standalone validator which can validate any data 
against an OpenAPI schema like this:

```php
$spec = <<<SPEC
schema:
  type: string
  enum:
  - a
  - b
SPEC;
$data = "c";

$spec   = cebe\openapi\Reader::readFromYaml($spec);
# (optional) reference resolving
$spec->resolveReferences(new ReferenceContext($spec, "/"));
$schema = new cebe\openapi\spec\Schema($spec->schema);

try {
    (new \League\OpenAPIValidation\Schema\SchemaValidator())->validate($data, $schema);
} catch(\League\OpenAPIValidation\Schema\Exception\KeywordMismatch $e) {
    // you can evaluate failure details
    // $e->keyword() == "enum"
    // $e->data() == "c"
    // $e->dataBreadCrumb()->buildChain() -- only for nested data
}
```

## Custom Type Formats
As you know, OpenAPI allows you to add formats to types:

```yaml
schema:
  type: string
  format: binary
```

This package contains a bunch of built-in format validators:
- `string` type:
    - `byte`
    - `date`
    - `date-time`
    - `email`
    - `hostname`
    - `ipv4`
    - `ipv6`
    - `uri`
    - `uuid` (uuid4)
- `number` type
    - `float`
    - `double`

You can also add your own formats. Like this:
```php
# A format validator must be a callable
# It must return bool value (true if format matched the data, false otherwise)

# A callable class:
$customFormat = new class()
{
    function __invoke($value): bool
    {
        return $value === "good value";
    }
};

# Or just a closure:
$customFormat = function ($value): bool {
    return $value === "good value";
};

# Register your callable like this before validating your data
\League\OpenAPIValidation\Schema\TypeFormats\FormatsContainer::registerFormat('string', 'custom', $customFormat);
```

## Exceptions
The package throws a list of various exceptions which you can catch and handle. There are some of them:
- Schema related:
    - `\League\OpenAPIValidation\Schema\Exception\KeywordMismatch` - Indicates that data was not matched against a schema's keyword
        - `\League\OpenAPIValidation\Schema\Exception\TypeMismatch` - Validation for `type` keyword failed against a given data. For example `type:string` and value is `12`
        - `\League\OpenAPIValidation\Schema\Exception\FormatMismatch` - data mismatched a given type format. For example `type: string, format: email` won't match `not-email`.
- PSR7 Messages related:
    - `\League\OpenAPIValidation\PSR7\Exception\NoContentType` - HTTP message(request/response) contains no Content-Type header. General HTTP errors.
    - `\League\OpenAPIValidation\PSR7\Exception\NoPath` - path is not found in the spec
    - `\League\OpenAPIValidation\PSR7\Exception\NoOperation` - operation os not found in the path
    - `\League\OpenAPIValidation\PSR7\Exception\NoResponseCode` - response code not found under the operation in the spec
    - Validation exceptions (check parent exception for possible root causes):
        - `\League\OpenAPIValidation\PSR7\Exception\ValidationFailed` - generic exception for failed PSR-7 message
        - `\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody` - body does not match schema
        - `\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidCookies` - cookies does not match schema or missing required cookie
        - `\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders` - header does not match schema or missing required header
        - `\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidPath` - path does not match pattern or pattern values does not match schema
        - `\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidQueryArgs` - query args does not match schema or missing required argument
        - `\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidSecurity` - request does not match security schema or invalid security headers
    - Request related:
        - `\League\OpenAPIValidation\PSR7\Exception\MultipleOperationsMismatchForRequest` - request matched multiple operations in the spec, 
        but validation failed for all of them.

## Testing
You can run the tests with:

```
vendor/bin/phpunit
```

## Contribution Guide
Feel free to open an Issue or add a Pull request. 
There is a certain code style that this package follows: [doctrine/coding-standard](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/latest/reference/index.html#introduction).

To conform to this style please use a git hook, shipped with this package at `.githooks/pre-commit`.

How to use it:
1. Clone the package locally and navigate to the folder
2. Create a symlink to the hook like this: `ln -s -f ../../.githooks/pre-commit .git/hooks/pre-commit`
3. Add execution rights: `chmod +x .git/hooks/pre-commit`
4. Now commit any new changes and the code will be checked and formatted accordingly.
5. If there are any issues with your code, check the log here: `.phpcs-report.txt`

## Credits
People:
- [Dmitry Lezhnev](https://github.com/lezhnev74)
- [Carsten Brandt](https://github.com/cebe)
- [Samuel Nela](https://github.com/samnela)
- [Pavel Batanov](https://github.com/scaytrase)
- [Christopher L Bray](https://github.com/brayniverse)
- [David Pauli](https://github.com/dpauli)
- [Jason Judge](https://github.com/judgej)
- [Yannick Chenot](https://github.com/osteel)
- [TarasBK](https://github.com/TarasBK)
- [Jason B. Standing](https://github.com/jasonbstanding)
- [Dmytro Demchyna](https://github.com/dmytro-demchyna)
- [Will Chambers](https://github.com/willchambers99)
- [Ignacio](https://github.com/imefisto)
- A big thank you to [Henrik Karlström](https://github.com/hkarlstrom) who kind of inspired me to work on this package. 

Resources:
- Icons made by Freepik, licensed by CC 3.0 BY
- [cebe/php-openapi](https://github.com/cebe/php-openapi) package for Reading OpenAPI files
- [slim3-psr15](https://github.com/bnf/slim3-psr15) package for Slim middleware adapter
 
## License
The MIT License (MIT). Please see `License.md` file for more information.

## TODO
- [ ] Support Discriminator Object (note: apparently, this is not so straightforward, as discriminator can point to any external scheme)
openapi-psr7-validator/LICENSE.md000064400000002077147361032640012473 0ustar00The MIT License (MIT)

Copyright (c) Spatie bvba info@spatie.be

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.openapi-psr7-validator/image.jpg000064400000076632147361032640012663 0ustar00���ExifII*��Ducky<��1http://ns.adobe.com/xap/1.0/<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c140 79.160451, 2017/05/06-01:08:21        "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmp:CreatorTool="Adobe Photoshop CC 2018 (Macintosh)" xmpMM:InstanceID="xmp.iid:345D36F9645611E9A2D7C25AC4D5E886" xmpMM:DocumentID="xmp.did:345D36FA645611E9A2D7C25AC4D5E886"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:345D36F7645611E9A2D7C25AC4D5E886" stRef:documentID="xmp.did:345D36F8645611E9A2D7C25AC4D5E886"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?>��Adobed����		





��m~���	
	!1AQ"aq2����#��67���BRbr��3��TUV��$4t�5u�WႢCSs���c�d%v82!1AQaq��"3�ё�Rb#��Br��$4��?ڐc���I��Y�Wdž<)_�{O�k���+�N��M��v|w���Vc|�>	�mX��
�`-�N���r�|t���Zg|\~��h��a���Ճq��y�Ɩ�T�xZ��KI�A��v�A��k�6����5�8�R��[�>�Ո�.�غ�K�>�{Ű[�6��N?&f�6��LI�k�k��
a7��鍞ӏ]��g��>92G�Zq��X���z��E��GL��N��N��5��ZlZ�_-���q�)��&g����&z�M{gE=��83^)X���|S���ٱa�l��\x���y�ֱy��D�]��M>Y�;���|&رe�~���'�#��X��I�t�X:�vO�2��:���N�V���1��}.Ӭ����N��������x��J���{�5�b���>�m<���Y�i�m��mNh����sG�E��=�r�i��t��a���S±<|>���k7}�[�ɥ��-�O�8d��D��0h:��~�m��A��l�gO�,��2=_�{��ܟM�[M�N��`��v�^��(�#Kx�k�Z�<�%btOM���;,�;'���Ϋ�����_�5cn^���eŖ��6���e����fسb�\7�LW�jd�ūh�<LxJEy���&:d���_�kzZ8��LLH(�F��u0��=�p��N>�i�[�՟����"�-��O�6,8��-�,u�^���b8���e+Z+3���ߺZ��&Mɒ�m�8���\��i_��&uxW���fi�xi��?�~�&�M�U���6�Z|��A�=qW��9�
��HN:Ck�o
6�>ڗ�o<�O5�~�<�<�Ϟ�=���=3k�k^��On��oTj����7�i4ԦMMbx{L��j�zb����ʶkt��l�ƜH&���G�b�m�o˫Îܗ�H��[�m1P�fz����뗿�o�_Թ��?�6�����1���Bu��u��>z`�3�/�O-f�>��ž*LZ'DY�RԘ�J��}�:�[��5Z}�=�d��w�#��X��q�ࣖ�6������D��7��\�O�yv�K���
ߥ��g�LۦߗI�%�)��Y�<8�f8���u��Z۪S��uF��~��y��SK��Vg���H�/�-X�?&�n�w��N%ֲ�����k�a�n���4���k��W���jG�bV6�ӣ�}�t�}�k�e��4�rn�����X�g?F���̼�~�1����G�n�K�S�1�<��=2�>z��Cd�����x���8����y���n-��Vz<+��B����h���j�Ŧ���K�ޑ���>�z?H�u�d'n�]{����,s�S����~�,��$����Sa�S�s���ʀ"��C��f�}X����حi�|�<a�K1��;��N��8���F��F���#ӿ�N#C�u�����'�'�t>>j��e�E�<k��œc�3��I�D��  �I�o�!�ʷ��+N�p6ϋ?���\�}9lR��u�_<��6�Je�Ǐ%m�"X��Vu��Q-Lצ1�b"g�yVϞ���g��7�f��m33�J4z<Z\Q�x�mi�̼��ql��w
⮐��Ijw|�͚'�Y��y&�?[O�Y�l-�u����[e��Y�z)�����a�4�b�ZR<�䇨�b��uC�V�X�:�m��^�~p7?�����:�_�/�?�?m���yT����2��ٱRx^���&b��?�-?}���h�Y�G�<`�{�~���sr���Ӵ�
���>D8��7_N[��3��O���C�뾋�^q�7����mF>h����v����Hָ�1�Yu�;�К���O��/��JΣf~(���=��z>��g�eŖ����M�1h��a$K�j�N���u~(�қ�&8��������f���U��)Ԛ������$c�9�y�i�7��E���[[Z4����/�"���ұv���:KFM]���<��\|sx�X��8��o�GK�c�M�i��^��mՊj��m,V8F;�8Dy��0�l���3�a�e�%-1��c�^��nZ�N?Z�՘G�3�:��zN�#_.��Y]>�K���4٩����/N&V+x�LN�W�jN����v�h�^�~p7?�����:�_�/�?�?m���yT�����=�r^<���|qS*+͏cɿ_S�6�i��Y��^y�ߌ��=
�ta���W�w�����#GWGNm.���FI���c�lw�Ÿy�!��z�U�?3������,G/,q�޿77?�޼:t�H{_̌z��I��|ǟ�^>>ӏ��y|�8������M�~$������[��h�8gş�|�qs.n���)q�Uw3��gMf˵l���Ӎs^g��������<��snb�������12���?�ߴx���:����-���y��i�nL�F*p��(�%��/��+m����"{��Z>����:�~ٲ�]�p�h�nj���O�<'趋Lu+n6xsF�+[{a5�߾��ߛA�ͧ��ϊ�o|�b2r޳Y�lS����	�sxps~#���j{'�՗�.��z{f�]��x��r[%f�Y�M}_����|���":��-���Ξ����zߪ�N1�ܳN9���>ʟF��b~���}�'5���m�'m����|�������: ˻G���Fm|�|��Ɋ�K}:�7�KVu��(��H�������ܴ���}��4��>uH��H��G�~�����W�'Lw��Ǩ�/��6��ۻ�e���h��LZ�Z��f�lyi<ba�钷�*α/�
��ix���n�՗W�×,�"�2�u�,��Z�b&ze��"t��jF�
u���s��}ύO/3���p��#����o�M��Oy��N�_�ٿqo�)�N�|�wNj�Y�����(WM�7���Ci�a����m�Q�j�M����%0�nh�36����f"b	{��]��S��t�~����'���&��^�Wd�T��ң3�[�r5[�,;Ƃi�Z���o�����g���p�L@�{�~���sr���Ӵ�
���>D8��7_N[��+>�w/Kl�m�97��-LY#�a�2Ǣ�<��~em�^�:���x�f���=����U�kZ�kLͦx�ό�˚��C�N�_�N���.��[���旹@�۩��cDR�+%���JJc�u ͞�㥓�K�/�����}T�o�����j�y�}�j�&<bc��՚Β��-o�c��˳��۵9'�f��KDφ<����>�~gO����3�o�\�}.3✕��S����z�c��ź�6�����~y�L�
���p�Y��9�E#�
�j��s�>ksd����C���kۊ�nU�6�e#�w�kˤ���LYg���.����_��W���~[u����^�~p7?�����:�_�/�?�?m���yT������ԷɴLO�)����K�$h5q\6�	���i��sEc�
�cG?�}a�:����2}5�Z��S�Y��\�����Vf�*x�mi��g�"D�  �I�o�!�ʷ��+N�p6ϋ?���\�}9lR��>�o��{��_����1��1��ۇǓ�\�����{Hò�v���h�!w@N�_�N���.��[���旹@�
0�0R�zE�}3h�2�c�+��k̽-б�C����$ǭ��5��xL!�*���4�F����v�mצ6�u�.L1\��ɏԼ�U�6y|�U��侩��w7�uD�{'���-�u���淭������g�U�n����_d!͚)(V�U�U�ٳ[���Q��g.[^�V�r�y��.�m\Z��f֞pH:k�)��h��˓Ƀ,����m�]���_��}������d��\��s�X>�Ƨ����8Y}��I�o7֦�ʧ��'��V�i��e2���֍6��O
����O��ۅ�]����5����p��[�w4�v�b�?i��dz��S��kۇ�\<\��xӮ�߻����y3<��Ǻ=�s���5��m-�X��pd�Z��LJ�kN:E��XswX<����\3��g��(�p�\���eN�c�= {�~���sr���Ӵ�
���>D8��7_N[��4[y�[S��7�6ͨ˒����3�\Y���v���Z�V>	�N�_�N���.��[���旹@]�qǗOM5��r�'�Տ'�9"cN�y�kn(�[��R`:�q�zF�
��'�-���䪮|������O��Vt��gs['GE'ɋS������\���:�L�o�Et���X���U�@-�u���淭������g�U�n����_d!͚)(V�U�U�ٳ[���Q��g.[^�V�r�y��.�m\^��f֞Y3S��kz)HI�h�G��>럚x��c����?Qsi�������ςʥ"��#�
�Dq����̽F���Yk�w?8�Ń�|jyy�}�Ӆ��ğ��}jl<�{�ru��6��}IL��lwl]?�e���.�Lx�MKDOz�xD�g��E���9�sv-l+�٫gLz�Z^�kql9��4�}��a���>^7�_5���}jюb�O��?�����ី�G�����鯾�E;��Y�kE�5œ��9����
��z}�>v^�z�{|g�!��Z���ۣN������l�[�Cq�.���w�c�U�1�6;^q��+�Y��9|8ǃ��&��_ݿw��b�C'��S����_�d�p�MV_m�S�.]�>�m��Nh�Y�<q5��ҳkO�3��ՉK� �I�o�!�ʷ��+N�p6ϋ?���\�}9lR��-ž�_���i���V�q_{�:�'��a 	��������K{\ܼ� h�j�V�%�^k������c������^=���H^e��1��G�=_�G���*����ӷY�M��M�/n3Z��/c�5ӵ�\������s[��o���}3�*�U�]g�����+U�Ϫ�l٭�{}(�D|3�-�n+u�W��u�J6�/zҳkO�,����5��$$�ѴC#��=�u��x��c����ֿ�\��g,�j|8f�౴�l\����8�Z��R�X�:�:�"4�kf@����n����u��N_d~������9��梳l+X�kR���&T@:_~��񩦫K[$�")�4��c��a�ukk�:���}�=�u�����%��{ӛ���|�7��V��Lt���/Z������/���t��SԺ�V:ϲ�r_�����k��S�'T�'Xn{ޣ[��#�//��l���8��r�i_/�b���2����j���˯������	��2����j�q@��C�vս���xˤ�Z2`�\�=��DLd�����bm~�@;�����C��og��V����m���!�̹��rإ� �}g��WozY��]��G�m��kƓ/�zvN=�;wҿ��	��������K{\ܼ� h��F�p�F��'5��y-X�
���L���/��ż����%<���A��d�c�[O�+�w5�]g�\�H�P�F�ϫ�ݲLf��I�^H��ɸ���3���-��]�^˽c��y8WUX���~ʮ��{cI�tpg�Ɠ�ɯ,����s[��o���}3�*�U�]g�����+U�Ϫ�l٭�{}(�D|3�-�n+u�W��u�J6�/zҳkO�,����5��$$�ѴC#��=�u��x��c����ֿ�\��g,�j|8f�౴�l\����8�Z��R�X�:�:�"4�kf@��s�X>�Ƨ����8Y}��I�o7֦�ʧ��'�Nײ��95l�oZ�����u}����o���G�I������qS9��""g�0;��߉?m��9�V�|��i�?���g��"\˛��-�\r��
�t�ݣ�
j�����Y�������//�~3����u��?���;��:_�~����ok���^�
l�Ih�E�^�c������X��F^�iNU�|�Բ����ﴲ�
 �n[�
i����ym?�����u��Yr�#YBu���͚�m>H�Dz!�3f�KqYʽ�Ӭ�4}�˓J��i��<kh�Ķ����u�1:�e��X���/º�ǭ_5��UzM��2Ɠ��`��'��^X��w\~kz�m�^?<Ϧ~]��k���B٢��j�Y�Y�5��o�菁�r���n�*��N��F���ZVmi��c5:�f���䄑6�dz{����zLs�\������]���O��|6�M�K��0R1��+Xz*R+GS�Z�F��l�]{����,s�S����~�,��$����Sa�S�s����a�x�ֳ1�#�eEy�lyw��se�M2Rk7���6��Lz���+�<����_�Lq:�:ٴ��o��l4��ǒ&���Dy�!��>�a龣�챳_Q:I�m�&h��6�[�k�W��ŵq�
�:3��z���,���ήmZ�ǚ3r�k6�j�S����-�H4Z���~$������[��h�8gş�|�qs.n���)q�k��4�v}�s⾏5��lV�����K}%
�zb_D�#s�2b�ɋG��~�T���e�d�q�r�n~���n^i{�4d:m��ht~q����Y�Z~�x�����Z�ʮ�?������g""""#�G�Cݾ>"�M�j��u�ӗ�G�>���-���}u�9۬V׋��r����.LY+������ڶ�α��L��	�˽c���
j��|֏�U�6{��O3��<^4��v����s[��o���}3�$�n����_d6͚)(V�U�U�ٳ[���Q��g.[^�V�r�y��.�m\^��f֞Y3S��kz)HI�h�G��{>럌�Ǥ�?e��~�����Y��p��ci��4�)�#,qµ���"��u:u�Di�̀�^�~p7?�����:�_�/�?�?m���yT�����U�m�����Q����|x���Y�L��t��~��_�d6��=I�~�۷��
N�}^}7
d�ɏ��n�<|��e�?w�g
���qZc�5#
g��pH�l������G~Z�_�O��#�d�w�F��b�q�s�͞-�~I��k6��Y��bk��5�~$������[��h�8gş�|�qs.n���)q�A���3���pŊ���F�Mg��G�8�ЃqN*{���o���-�Y��~�5	�}��e�d�q�r�n~���n^i{�4X���gS�j7\��C���._���M��\Y&����w��1G]���t=;���ҷ�������>11,LDƒ�ƨ����Giς&�[O�4��O����*���߇�:�w9X�֥�ՙ���1�0�L�L:9ɓ&Ks���嵦f~�����3�33=o�q{֕�ZxDyd�N�٭�y!$F�����~3����/����~���k9g�S��7����`���x��
����Ƒ��ֱ�;2
u���s��}ύO/3���p��#����o�M��Oy��O�X�-K|�D�R�Q
��
&\��q\6�W%��b<���ڱ�����?��CX>��Vߪ˭��+5��;T�i��g�#+��]fiͫ�i��"'&\T��G��bd՗O�MUh���Y�~����!��&���5�U��C�Mdwi6M�G��i4:}>^=�,T��O��b$�{X�~$������[��h�8gş�|�qs.n���)q�&"bbc�O�ăN;����N��h)I��4��o��pd���~�x��99i�mg�/Q��ڷ�x跶?~�E����/�?N\
�Է����/r�Zͦ+X�i�,��΍��>����zm%��U�����q��X�_���
���+��/�z��w�r�E}����+�X�ҷ�������>11,LDƒ�ƨ����Giς&�[O�4��O����*���߇�:�w9Xq{֕�ZxDyd�N�٭�y!$F�����~3����/����~���k9g�S��7����`���x��
����Ƒ��ֱ�;25׻��
��>5<�ξ������O�y��6U=�?�?L���ğ��rܫ{>r���l���ϑ.e��Ӗ�.9 =���uML�)���ٴ�Lz�x��#��7�������W�<�7ҿE�;���jVLy1�<��d��oKG	���11>I�-�蘘�# w���t���9p7?R��7/4����m�q��YO������<x�Ч����>��㷙nZ����-�'�yX��O�~�
���u�C����4�E�h�jόLK1��1�!�lv��s�����&|��<�c8犼�3q��鎦�V��ZVmi��c5:�f���䄑6�dz{����zLs�\������]���O��|>�M�K��0R1��+Xz*R+GS�Z�F��l�_{ˡϧ�|���c�,��春#�)��h��m������]���=��}�T��͏&<�I�x�1h�O�Ǘ�4c>ێu�%��������7�}����ݿ��?�C��>�{����M��Z~��c=��o����?P�����Sg�V��y���y�}����+O�<��g�����������3�~_v��l���?���?/��6�i����}����ݿ��?�C��>�{Ѯ��>���`��f�l�2d���D��b=-2e���pN��;5�Ϩ�|�Vg�\�m戽'}9��>fwvҞ�-� �{�ٜ���/M���g��4�����O�W������4=����(������'�����k��z�oY��f8LLxLLJ���:���d��������ˁ������y��@����=Rj��lV;�j|��<����o��lv�:�S��q}_�)���Ӓz��χ�~h4:M��I�1i�V)�|�����+�DC�y�[%�m.���oY��-[GV|bbX���%��Q
�c��ӟM����i3柁�w��<U�9���Lu0׽)^kO�9Y���m�ފG�Dh�!���Ϻ��<q�1��r�k_��.mv���>3y�X�m6
.
`�HNjp�a�H�iN�kC�� 0}[�WS���D�&9�i�4�ώ�����熷�Z��4�aV�{�5�1��4�1~��=�;p�kZd��W�%v7��u~Bz��n�����<�g�i�'�'�������ȓ�i�'�'�������ȓ�i�'�'�������ȓ�i�'�'�������ȓ�i�'�'�������ȓ�i�'�'�������ȓ�i�'�'�������ȓ�i�'�'�������ȓ�i�'�'�������ȓ�i�.�7a���b5;��/�[��nmLq�'�,N���+K��?j鍾t����1mF��9�Z<�xy"<бJEaK.Y��,�d@�ݘ�n�������bc$����E�>1?
�zۧ�^�ҿ$�m#����g�{>
�S��յ�1��tqy��sc��k�$~���[����m�x�
'��Ѣ�_߯1���K�<��L��{&ci=��'�c���1�A��`��V>y���[�8i�ڌ��g��d��4��{fg�H0�'�t�\z|�dF:��לV�|)UL����3���?�]���kO�x5}����7h�Er��j����B��~�x�/��c���|�G�9�5�9��m�'�{4?�&^�����~[������9��WE��\�ί�|����bi�x~���L|\���X�o:�9����h�
x|g��W�di��}6
i��8�NJ��b<��ֱX�:!��״�Ӭ˱�@Z��f���m&'�&���$�F����rNM>��~><�ұ�q���nnOK�ε�Km"z�:N����mV�٫��y"~9�ik����N�Wi����4�k�#*G
Ҿ�i�#�n""4�cf@�{�~/t��^��Uw���Yѿ�=��<`���B�&�f�<����Q��M+�
�a�5�"���m1��
�m����j���H��ixz�t��j2D�&;���~
�莻鎶ة�t�5Z;Zq䉎\���D�<���"�?�π^���w��.��kM��l�|�����X����O��3_��0/��M��i��[�M6�3`�Kc�X�mH+.�w�j펿m��6�ۖM�L՜9i��1�+��ǏW_߃�?����_���=�{i��-�pǪ�s昮<���m7<��sR����b>L;��
��[F�q�h2n1�j'O���1�I�ۍ�xLJB;�_�u�X�3�a���W�:jrf�z�k��<kZ�<�{�����nv�Z�5�S6��o��_Q�Ԏ6��b+^1�kLD|s
B����M)�gG��3N������o5��s�\��wJ�3�6|�&�o�[Y��R��\�1KW%-z�'�}��������z��T���gF���߸�T+��n�Ѡ�u�~]�5������j��o6��O��T�߃�?����_�o���s�k1iw]�f����L�)3<#���#��˽u�Ml�3����-��>}�K��8�/y�4rEx�q��o>�
]���-��ibf#>�WM%���Lc�-O��&=����ߴ�=m�Y���5�R���Z������z���jD����/n��zwS��7����[>ԥc���^��1:=�:o��K�8y��1��{�v�7\[6)�m;�y���եk�߱Ǘ�^oEm�g����~����ܶ|�7�͓m�g�_-s㈼��lsh���^ �~����?�1�f�Ͷw?A�j�[~]�v̸�dś%rM��fմMb?c �n:��v�V�՛�K�&kR8��Y��>o@5���t�_Y���� ��+��[қ_Q�"i���W=q�b��i�;Lxq��k?=����6M~�&�n���j-�L4����k�����쾳�c�A��1�b�oj�1b�{����2|�q�rr[�7	Lx7mޛuqM��I�3�"xp�����a�X�T9�ph��I�Y�Ǧ�I�Dž��>H��|6��/�MQ��E���F��n�QlS�r�y'�^›w��:k��6~
:[gP��j�Og�fb�h�'�G���n4k�s�4e���ۨu�=m�c�H�^��[��<��j��C&;�b#OT�nm[i�b��v��ޱ7�S�i_�LJ��g�'̘��U��8�
��k�v��k�jڜ|8p��T�o�3��om&�Qm��w�f���*ږ�6���x�<&��8aWq���nŹ�79o�����X䉈�Ï�e��slՙ��
��f�:�k�;�^���=	�oآ'>ݡ͛O���I�Q?��������_n��Rb��j����G��m^�$�o�-�/�{p���'�
��?i�_��t�M�M�w�ir��ux�SLy�W���q��4��=5��o�w.�ݺ��4����d��5��6�ɩ�p�(��9�{�f+3�:�}�o�q�]禫�n��9ris�\�юӎ���J��"��6���{����5�����[>�)��㇖g���A�ֻW�v��m�WI��#���I��H�Z����y��X��l�M�OF�yz�&;ړ����I�q��)�&J�|���y��n�{�mg�x�cy�/�o:�[�c$�Ó%#���1���x�G���o��Z��;��1�Ŷ�
j3�Ɋ�"���{?
��|�c�������{-�������uZ�G���Ǐ.*S'
�&o��Z=�����:�=t6ڷ�k�Q��H�Kb���LY9q��D�ÌyA<�ޡ���K�����K�Uj��q�mZ��i�
����=�����2�t{���5\�6��'<j�ÄV'&��c��3�=����d��lo�e��U�}<�Q��H��:��4�l���gɆ�̓U��mX�Rm�+nKO�x�����m;V��[�k�X5q���>*d���D�qi�wѝ'�}m��`����譛[��R��E��k\��X���x���}<Axo-�{�ƫs�u:���y}�jŭ�1�L��|��`V���m5�n��_-�p��ޑ{g��|c��8��m�����>���=�?;�`��7���r��W�gD�gc�׶��;A��mu�l�2m�}�
^���{c����6�7,����n�흒������Y�K:��zF;_�x�_�s����b>��k����&㎻�MW�˒r��眹���kK֕��Ek<m������_�k������}�S���,�7�d:3�v.��}R�f�v��o�G%�ړ5�L��-��ǀ).���ٴ������Lw�'Y9',�H�i�JS.L��S���Lj2ݦ���:Ϩ�t��_b�u6�=$�I͇&JG�i�c�;�	�8�������+�Z��j1_tߵ��v��&���|��u���fg�
����̓&��~�C���;�Uz����7��~1���l�N�87Ο�7�>;�i0kqa�)MF:�o3��<$����-����@]����to��m��%@ߍ.�Q����i��m��c���rޕ�[׏��>I�O��5�ί��M�SHš��jW$G5���O���
ѻ.�`�v��l����������ZV��ԤEy��b9�����^��oZ�t�����}&
�>�Ǘ��#Z}�~ M}�;��M��sl��}����ɨ�k⴦��F>��%b���Z����,~��x���W�.ݻ�[s�Z]k��|w���nL��Z�����z����zU�i��-�3�Ǵ���7���D��ݏ`�w.�lѹm�]eo�[>q�X�|�,x��x�W����[_{�A����h�qif-J�1M������y���À?C@I���t�����[�ͨɩ��'m�Z[e�E�yg�[�}��"x��+_|��Ǣ�h:l��:��_��:�L��4ح�<�)7�>�} ��ۺ�?��v=F?g���%�q����1>NL1H����
�Ii���C��8r�t��,���K�|��G�FO'�H-�r���_N�#���.ך5��>�S��xx�Z�O� ���I�;iM�O�+��=Di�"�-�\2�G�bm��?��;WJa����Nf�������S$E��r[MmDLOVr^�"|��N��z�ro��iǦ��->�E�g�13\X�3X�LD�f"#�0
~��]^����f�~I�e�^'��-z�:ZxLJ�/.�c�;��Y7��/��-���p��G7��յ&�1h�	��U{ス��j���u=t���NH��D�8���-i������:�����m�t�[%"њ֜wņ٭p�Ŋ�qϞ~ e{��)�tV�^����EԜi\�L7�x��'	�9�i���5��Z����/�Rl��z��un<��y��i�mLy�W����>0Z���O��C���t:�n�u�Kh�b���Ey��Kϩ�rV<"g�;��?���ì��/�۳�2jr�2Vc�re�����_��܎���i�M=��>�E���Ö"-ÌxZ���kh�	��A���?�}�_a��/�o�b���\��h��N\�7�<�_�-=�wMr�{蜚l��6����Z�1-p���ڼ~ \���{qo�`Dz��:�j�l��,zkLZ�kO5x�Ro���rnyt���}>i�V|��kֳÞ�qS-�O�։������ru�vl��,V�M5�{|9��xZq��y��&kj�DŽ�|�w;�>�q�;�T�TF���[�1�ܞ�
0��|�}����q��mc������k����f��X����7���3�v����?��?������݃c��g��Ch�����V���\���{8����~^>�ipF�IMV�&DLڳ�3�	E�d��Zd�f����+a�p����>X�|~���Ök=���'K��unz�G����"�_���x�E{�>��,C�l�^�]^jz׎{�b9�/>�}F�n8�?�3�Lx���V�6Ť�~�m3�B	�[u�z��{!��w�:��)������&#�>{M�rΚid�w~��cz��/��
^��_z����k����>x��=Zז|�>����m�m5�M81D��t��W�t��z�y�8�3��'��n�����z;��ZcH��u�4�|�&��<��Ä|3�Y�ok����sn"��r����_O�y���Ϛx����)5�骞]�ںh�t������\�K{Sl���u�@��93vk�)�8�46���ij�ߡPP�����:�GkDj3i�����4œ%o?Br��i��U�I�ۦ�=�\}&|�m��+Z�o��K=�4v���3M�U��H��x�~9A��}�U�O�mVy���;�����3L��_V�1�ڱ1�`��6��7g��r��4�-�y8DZ�{{+V��^|?P7k��]�{���|Y/���\<-14�ut�{�ϛ�ِC��z��:+Y�n'F�:�S�LJ���/-"okW�,9���^$�N���w�=���tF�bɓ_�͞���*|���ɞ���=\|fg�;��cӳ��v�g�^A����ܽ����I����prV)�r�9&��6��3��{�Zc�:�����b~��y���1���GӘrDjw�LNZq�?6�Ld��Ӗq�~R��.��{;���`��2n��?��ˋ[6��i�+5��*WG�{�κ��Gq�?�$WK�:kSg��֖'.?�8��������+�������ٜ��T��u;e'f���<�So��O��\NH�/�� ��~�}��~�����港1�jj5��'�qc�<�1�c�oW�<A~��E��O��ZLUä��p���8V����b=X�
7�o���?��j� ��S��'ܝ��z��k4�3���7j��1j[��Y�����X:����A��M�O�h���.��ڮJG4�b��K�W��d�>h�f���Ԗ��N�W�W�f�dˠÚ�y�d��yqL�֖�W��#�!�}�U�O�mVy���;�����3L��_V�1�ڱ1�`��6��7g��r��4�-�y8DZ�{{+V��^|?P7k��]�{���|Y/���\<-14�ut�{�ϛ�ِC��z��:+Y�n'F�:�S�LJ���/-"okW�,9���^$�N���w�=���tF�bɓ_�͞���*|���ɞ���=\|fg��ҝe�Q�E�D_K��#C�O�V�}x��xxdǃ�787��[�]o�C�������1�˛O�$V/:{��_	���O�æ����c���Nj|��c�b>(�v+.��Gs:�nڰ�&�;�K�%��cY��{JZ��6���p�����{��I��n���������ݿ�g��s�6��7]�4��mVl��c�v�[��Z��ܺ�R+�&<j	�I�O�Oq�m�p�:�t{N�C��m�O���/e�ˆ�ŏ���4�%&#���<��A=�?�$�Ʒo��{��I��n��e�KA���+�m���盶�C����{��XkL�=�H�ߞ�3�h�>pe@~��h:��ٟ�i�d���}��[��\}�)����'��<��/?� ����������C���6�.�i��O���fqD�f�6f�������h�Q��t����^f����c,ߍ�����n��%ܱN\�%����ˋE�Rq^#���E�i�Ө+�u\����6l�2�1�pǣ�v{[�J��創y���L��<f{{�#����s����n��;��v��/�bǨ˗e���->[S4�c�s�I�G�	�����]wRg�~�6^���N�mE�m,b������y�|<k��Gy���.�޴[wLtnm�O��F�6���f�o7�=�.
O-��q�x�m����[�K��k��h����^<=l�")H����d5z�6�G�Y��t�|v͟-�iJVmkL�" '�mOz}�'W���m��]���[���'NJ�i��J���ۈ7�""""8Dy j�KcǗ�7Œ�|y1�kzO�MgGH��W��7h=�gl�6��Mm�ܳ��hu�����>L_K~����{��W���}�e�*����/6�Fi�٭�
�����_i��͵�Ӄ?﷽j2u�O�6�>�n��#��7��)���i��w��M��7Fi;U���y6��|�6ǟ�b�mO$im[{K[�|�?��t΁��&�ָw��W�hw}.,��W�{d��ڔ���o/?*��[��u6�,�ƙ�0jo��l�
V:�f�Ǟ�����>��;߾�uWp��v�m.��;��
>��c$j2Z�H�{MizZ�X�|�|�A�Dw/�{ê�ţͨ�f�ƿC�`��b��#&-E"'����p��'����k��=�{�Ѻ}�6�c�i�m}<�ՙ�\y�c�bf#'��<����w.�jz{c��r��[�6;}����Җ����{;V)�kDyZw�����Zޕ�:[v����-.۩σ�pF�Ԯ9�K��|8�=~�92N>��6��Y�-Zq���f#�<�
��8t�S�M�v�l��}~��s�q��/j{Jۏ�;_��@.�}h��n�n1�`��?	�j�Pv�z�m���v��#vޯ��5��eͩ�>������.�WC���b�WR|�W7ͻi�?�c�k��Ń4z���+��@d{K��w�{�iz��'��t�z�n��t�p�L�2R���)�-6�N"8�'��~�Y۷��X�I�sG�i�����<��f���^X�ɯ�۽L��ZZ�W����Z~���O���G�~��"�4Q��]W���h��c�G_q��/ʽ�����a�[�ե�lm6kb��O��3����9�wة4��U�╍"%���xxxD�>K)l'���J
�ԇ��?��W����u����'��]��=����?�x���>����/��O���W3�~���®Ϟ}��DF���L�5��1�'�5�/��|��'��io�/OzAԱ���q��P�џw�wu�/'H���}D��oj-�T����oV�w�u�N���E�`ɥ��<Ԛ^>���z�������|Ke���|Z}���m�K�xrd�xr��k���Ï��H���g�{��e���i}ξ�Y]��jr㙎jc�Ҝ�������W�/f5��j�=��N�ޣ����|�ON3LV��ͭ�~y����
�o~�>���u��7��,�c'��ųe�~[�\~H&��GUw����~��c���]E�ح:�����R�&��1�=��������u6���[��ܷ��n:�~�+���1�(�Ɏ�(5��:ϸ=���G�l|��ָ�ZMD�<��fq���Z���Y�x�Au���[�������o�c�[4�y��MO��8��i�5Ï-�m<�fc�<d߽_\_�;��m����q}��-~{g���/����3��vt_z�A��N���%�q`�~o�b��8q��SQ噊L�1�ǚ&>kϻ�]�Gw;A��뫺�v��o�j|�6(���-��5�W������#�pn��h�zq��m�����&��
����i����
���QGRt>ɾ�G}w
&<��V�Ҹ�c�V/¼c�xx�:�?�WW֛^��N����z^o��S$^���)Ǟ�<�����V���C�Zk�2�.�mn[[7?����)�E#�2p扙�>@`=������Kд�)��u��\d�2���ۗ�սb���������/L��.��5Q�vɶ_-�4�1|V��"+ni�|�`7�'\���~����4�x���	�S.nn^�<c��
=��ٺ�;���M&٬�ѷi4��p�\ɂ��i��&1�^���ǀ>��|ޡ�v<�F���{�����Ʀڞ��qǂ0㙷������w�;��K�n��ߨ�6�Ξ�}�E�93��Ls�9��^X�[�x���ÈR��
�o~�>���u��7��,�c'��ųe�~[�\~H&��GUw����~��c���]E�ح:�����R�&��1�=��������u6���[��ܷ��n:�~�+���1�(�Ɏ�(5��:ϸ=���G�l|��ָ�ZMD�<��fq���Z���Y�x�Au���[�������o�c�[4�y��MO��8��i�5Ï-�m<�fc�<d��go���������.L�^�U�u�k�Rצ�W�k{�4��-y�3�&'��A'��r�ZD�_�ޱ�l��M��5�LV���~0mɮ��}�U�ɡ��pc˟E�8dÒ�_��Ɩ�h�o~��K���.��k:7�Ƕ�nj�M{�u�^��]=j���l�[^ib��y�DV���8�5����v�s�������4�lԶKb�NK�+8���τ���=���l�l;v�q��]�ƚJ�Kִ�jc��N7�x���Ì���/{
�:;E�z�v�i����YuV��+��\�g
�ԭ��*<��Ϸyݎ�S���
�{&]e7~�ԜX�V�[�����ּ��"�3<xq	��\��owE|�)�C\{�Ϲ�my�\��=�/�W����t��j�3��~}�]
1j��p�������Ɋkj�&o��0
����߳=�m��+�jcp����,�<1O����|&A�]��Þ����V���$Ζ׌���-�ۅ�W�f��c�<)#�iױӽ��˦�ɹu%�IX�^h�Ӆ�6�>kVc�``��:�gIk��W��W�d�)�xƓO<&c��f����_����Q��NI��_g�m�Y�,L�xC�[�:�}��_�猗ݳNmt��Z�)\�[V"ֈ�>5�����F{�c��SWKm������.	��'$�o�?k�ch��n���s��f�W��-�q�ju6��p�k��H�5�o���]دp�ALY�i�:�ђmN:[�q�NX�[�x�/��{���7Dt�_l[v[��4ͭ�)���c��9c�f��i��b8�����:��zgE�;�Ob�2l��Ú���\X#���Rq����y���}s՝!���Ndڲi�|�.�1䏜a�jֱ�y�[�V}h�D�DG�AJt���P�7-q���7�H���&)����ly8DŸׇ�<|�|1�-�w?��:�b��=/�q��g�T��M$��J�#-�5�x�c�֙��a�Ǽ�\�Oxu�v��N��f�,:Oa������{��,�z&<X�?�c��Y�a�n�ɦ�a���s�'6y�j�[Slq�1rO^�y�w�c�S��!��L��ϳlzm�[��I�`ϥ�M�^�ǒ�}�����g�T��~�����N�~L���G�o�g�d��<\����Zz��/�=�W� �{����u棦�m�9�u�v�׋icE�=�n-l�����z�}��u�H�����m��.j�\z�~}>�8�W�ojL�DLx���'����_Yt��'X��zs��4y��ɫ�9}�	��&K}��|�W��wn���t�Z9��Q����i�YσQkV�3��4�����m;��ݔ���;�O�\Y�Χ&�W7��Ǜ�1[%/�x�/\q&bxxH/��w뭻�ձ��+;KWM�r�4���]Mf�H���X�p�W���q��$H/0�����o�s�n�/��z�U�N�o��i��S
)�`�lu��[[՟?("��/z����n�́�����������/z����n�́�����������/z����n�́����������6?����i�
�m�u�?�t�\x���i�7��X�k�L�\��>{Gf����/6��#��-�[�0�-k�R�S�%��7�k��2x�:��>� ��M�M籦ҼV�K'Ժ\���#M���{R<�M{ԱZ��^��>�j�0�F�m��x�K��5���xG	���7�Uxxu�U0�8#MM>mN�r�[b�=&g�[�|������ز�1�w{Q��K�K����Ϭ��'��f|��X�,�W�ӗ���d��w=�m�kӷ��OǏ��G��,��鶞���-s�����gͷ*z����>Ç5����?7X�׋��7��;��&�G�N����k�Z_����?E.���㎭uo���O��}͸���b""�o6���G����}9�5��Y˸��H��Jj��=�я�,�ml�>��x6����,��:蔻Έ�V���������ă��O����L|�+��^<=<���N����}N=�I�Y�O�b-z��9&"g�<+����\�Wt>��=�p�n�m�b������$e��~�t^���[k��O�bݴ��u��/5k��jŦ�� ̓�.,Yk˖��_/-�&8�1�ǎ�Lu�R<��DDy���uWK��Ok\4�O��[��|x՛�>���ɂ��{V�6�&��Z���rLDτxW�����6��}�{N��-��$�f53y�?i�>H������I��׿�t�2Żiqj맞^j�-bՋM|'�A��\X�ח-+��^[DLq� cǏ"���y+X��� S111�'��ŧ����S7��W�O}�5�߃�{���?�����΍�q�q��ue��rۛ.d���Չ�F�붛Ml��ةl��b�X�q�'�;חM��1l���c�&ՋO��ִ�V��xEb8D@8ˋZ��rW��h��?DbÇMqc�8���V8�}�D����,�x�,u���W��8��,u��3�g�Qw��w�{��j��A�����a��9��\6�7��|9��洈�,Ă3�>��䍫�z{���Ã?�cOY�N�6~I�^�X�֕��>_7mGc�#[�]�ٶ-��J_Q��LZ)�Q��m��xz�h�����0f�q�$G���nLU�kX�b+Z��� LD�	�1�A�V��DG��F��Yr׏���cH49kǏ���9db&8O�Ek�""'�Ɔ�b"#�G�C:�=�;?Խ�ڶm&ũ�i�m��e�:���Y�JV���f������&�a轃c�_M^ն�4Z�ᙜvɧ�LW�M���ͫ��#�h��L��^�zO���1?BA�O����b�>>^J�x� v��