<?php
declare(strict_types=1);
namespace Viserio\Component\Routing\Dispatcher;

use Narrowspark\HttpStatus\Exception\MethodNotAllowedException;
use Narrowspark\HttpStatus\Exception\NotFoundException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Viserio\Component\Contract\Events\Traits\EventManagerAwareTrait;
use Viserio\Component\Contract\Routing\Dispatcher as DispatcherContract;
use Viserio\Component\Contract\Routing\Exception\RuntimeException;
use Viserio\Component\Contract\Routing\Route as RouteContract;
use Viserio\Component\Contract\Routing\RouteCollection as RouteCollectionContract;
use Viserio\Component\Routing\Event\RouteMatchedEvent;
use Viserio\Component\Routing\TreeGenerator\Optimizer\RouteTreeOptimizer;
use Viserio\Component\Routing\TreeGenerator\RouteTreeBuilder;
use Viserio\Component\Routing\TreeGenerator\RouteTreeCompiler;

class SimpleDispatcher implements DispatcherContract
{
    use EventManagerAwareTrait;

    /**
     * The currently dispatched route instance.
     *
     * @var \Viserio\Component\Contract\Routing\Route
     */
    protected $current;

    /**
     * Path to the cached router file.
     *
     * @var string
     */
    protected $path;

    /**
     * Flag for refresh the cache file on every call.
     *
     * @var bool
     */
    protected $refreshCache = false;

    /**
     * Refresh cache file on development.
     *
     * @param bool $refreshCache
     *
     * @return void
     */
    public function refreshCache(bool $refreshCache): void
    {
        $this->refreshCache = $refreshCache;
    }

    /**
     * Set the cache path for compiled routes.
     *
     * @param string $path
     *
     * @return void
     */
    public function setCachePath(string $path): void
    {
        $this->path = $path;
    }

    /**
     * Get the cache path for the compiled routes.
     *
     * @return string
     */
    public function getCachePath(): string
    {
        return $this->path;
    }

    /**
     * {@inheritdoc}
     */
    public function getCurrentRoute(): ?RouteContract
    {
        return $this->current;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(RouteCollectionContract $routes, ServerRequestInterface $request): ResponseInterface
    {
        $cacheFile = $this->getCachePath();
        $dir       = \pathinfo($cacheFile, \PATHINFO_DIRNAME);

        if ($this->refreshCache === true || ! \file_exists($cacheFile)) {
            self::generateDirectory($dir);

            $this->generateRouterFile($routes);
        }

        $router = require $cacheFile;

        $match = $router(\strtoupper($request->getMethod()), $this->prepareUriPath($request->getUri()->getPath()));

        if ($match[0] === self::FOUND) {
            return $this->handleFound($routes, $request, $match[1], $match[2]);
        }

        $requestPath = '/' . \ltrim($request->getUri()->getPath(), '/');

        if ($match[0] === self::HTTP_METHOD_NOT_ALLOWED) {
            throw new MethodNotAllowedException(\sprintf(
                '405 Method [%s] Not Allowed: For requested route [%s].',
                \implode(',', $match[1]),
                $requestPath
            ));
        }

        throw new NotFoundException(\sprintf(
            '404 Not Found: Requested route [%s].',
            $requestPath
        ));
    }

    /**
     * Handle dispatching of a found route.
     *
     * @param \Viserio\Component\Contract\Routing\RouteCollection $routes
     * @param \Psr\Http\Message\ServerRequestInterface            $request
     * @param string                                              $identifier
     * @param array                                               $segments
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function handleFound(
        RouteCollectionContract $routes,
        ServerRequestInterface $request,
        string $identifier,
        array $segments
    ): ResponseInterface {
        $route = $routes->match($identifier);

        foreach ($segments as $key => $value) {
            $route->addParameter($key, \rawurldecode($value));
        }

        // Add route to the request's attributes in case a middleware or handler needs access to the route.
        $request = $request->withAttribute('_route', $route);

        $this->current = $route;

        if ($this->eventManager !== null) {
            $this->eventManager->trigger(new RouteMatchedEvent($this, $route, $request));
        }

        return $this->runRoute($route, $request);
    }

    /**
     * Run the given route.
     *
     * @param \Viserio\Component\Contract\Routing\Route $route
     * @param \Psr\Http\Message\ServerRequestInterface  $request
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function runRoute(RouteContract $route, ServerRequestInterface $request): ResponseInterface
    {
        return $route->run($request);
    }

    /**
     * Prepare the request uri path.
     *
     * @param string $path
     *
     * @return string
     */
    protected function prepareUriPath(string $path): string
    {
        $path = '/' . \ltrim($path, '/');

        if (\strlen($path) !== 1 && \substr($path, -1) === '/') {
            $path = \substr_replace($path, '', -1);
        }

        return $path;
    }

    /**
     * Generates a router file with all routes.
     *
     * @param \Viserio\Component\Contract\Routing\RouteCollection $routes
     *
     * @return void
     */
    protected function generateRouterFile(RouteCollectionContract $routes): void
    {
        $routerCompiler = new RouteTreeCompiler(new RouteTreeBuilder(), new RouteTreeOptimizer());
        $closure        = $routerCompiler->compile($routes->getRoutes());

        \file_put_contents($this->path, $closure, \LOCK_EX);
    }

    /**
     * Generate a cache directory.
     *
     * @param string $dir
     *
     * @throws \Viserio\Component\Contract\Routing\Exception\RuntimeException
     *
     * @return void
     */
    private static function generateDirectory(string $dir): void
    {
        if (\is_dir($dir) && \is_writable($dir)) {
            return;
        }

        if (! @\mkdir($dir, 0777, true) || ! \is_writable($dir)) {
            throw new RuntimeException(\sprintf(
                'Route cache directory [%s] cannot be created or is write protected.',
                $dir
            ));
        }
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Parser;

use Viserio\Component\Contract\Parser\Exception\FileNotFoundException;
use Viserio\Component\Contract\Parser\Exception\NotSupportedException;
use Viserio\Component\Contract\Parser\Loader as LoaderContract;

class FileLoader implements LoaderContract
{
    private const TAG_PARSER = TaggableParser::class;

    private const GROUP_PARSER = GroupParser::class;

    /**
     * All directories to look for a file.
     *
     * @var array
     */
    protected $directories = [];

    /**
     * A cache of whether namespaces and groups exists.
     *
     * @var array
     */
    protected $exists = [];

    /**
     * {@inheritdoc}
     */
    public function getDirectories(): array
    {
        return $this->directories;
    }

    /**
     * {@inheritdoc}
     */
    public function setDirectories(array $directories): LoaderContract
    {
        foreach ($directories as $directory) {
            $this->addDirectory($directory);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function exists(string $file): string
    {
        $key = \str_replace(\DIRECTORY_SEPARATOR, '', $file);

        if (isset($this->exists[$key])) {
            return $this->exists[$key];
        }

        $file = $this->getPath($file) . $file;

        if (\file_exists($file)) {
            return $this->exists[$key] = $file;
        }

        throw new FileNotFoundException(\sprintf('File [%s] not found.', $file));
    }

    /**
     * {@inheritdoc}
     */
    public function addDirectory(string $directory): LoaderContract
    {
        if (! \in_array($directory, $this->directories, true)) {
            $this->directories[] = $directory;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function load(string $file, array $options = null): array
    {
        $this->checkOption($options);

        $parser = $this->getParser($options);

        // Set the right Parser for data and return data array
        return $parser->parse($this->exists($file));
    }

    /**
     * Get the data path for a file.
     *
     * @param string $file
     *
     * @return string
     */
    protected function getPath(string $file): string
    {
        foreach ($this->directories as $directory) {
            $dirFile = $directory . \DIRECTORY_SEPARATOR . $file;

            if (\file_exists($dirFile)) {
                return $directory . \DIRECTORY_SEPARATOR;
            }
        }

        return '';
    }

    /**
     * Check if the right option are given.
     *
     * @param null|array $options
     *
     * @throws \Viserio\Component\Contract\Parser\Exception\NotSupportedException
     *
     * @return void
     */
    protected function checkOption(?array $options): void
    {
        if (isset($options['tag'])) {
            return;
        }

        if (isset($options['group'])) {
            return;
        }

        if ($options !== null) {
            throw new NotSupportedException('Only the options "tag" and "group" are supported.');
        }
    }

    /**
     * Get the right parser.
     *
     * @param null|array $options
     *
     * @return \Viserio\Component\Parser\Parser
     */
    protected function getParser(?array $options): Parser
    {
        if (($tag = $options['tag'] ?? null) !== null) {
            $class = self::TAG_PARSER;

            return (new $class())->setTag($tag);
        }

        if (($group = $options['group'] ?? null) !== null) {
            $class = self::GROUP_PARSER;

            return (new $class())->setGroup($group);
        }

        return new Parser();
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Filesystem\Traits;

use Viserio\Component\Contract\Filesystem\Exception\FileNotFoundException;

trait FilesystemHelperTrait
{
    /**
     * Require file.
     *
     * @param string $path
     *
     * @throws \Viserio\Component\Contract\Filesystem\Exception\FileNotFoundException
     *
     * @return mixed
     */
    public function getRequire(string $path)
    {
        $path = $this->getTransformedPath($path);

        if ($this->isFile($path) && $this->has($path)) {
            return require $path;
        }

        throw new FileNotFoundException($path);
    }

    /**
     * Require file once.
     *
     * @param string $path
     *
     * @throws \Viserio\Component\Contract\Filesystem\Exception\FileNotFoundException
     *
     * @return mixed
     *
     * @codeCoverageIgnore
     */
    public function requireOnce(string $path)
    {
        $path = $this->getTransformedPath($path);

        if ($this->isFile($path) && $this->has($path)) {
            require_once $path;
        }

        throw new FileNotFoundException($path);
    }

    /**
     * Check if path is writable.
     *
     * @param string $path
     *
     * @return bool
     */
    public function isWritable(string $path): bool
    {
        return \is_writable($this->getTransformedPath($path));
    }

    /**
     * Check if path is a file.
     *
     * @param string $file
     *
     * @return bool
     */
    public function isFile(string $file): bool
    {
        return \is_file($this->getTransformedPath($file));
    }

    /**
     * Create a hard link to the target file or directory.
     *
     * @param string $target
     * @param string $link
     *
     * @return null|bool
     *
     * @codeCoverageIgnore
     */
    public function link(string $target, string $link): ?bool
    {
        $target = $this->getTransformedPath($target);
        $link   = $this->getTransformedPath($link);

        if (! $this->isWindows()) {
            return \symlink($target, $link);
        }

        $mode   = $this->isDirectory($target) ? 'J' : 'H';
        $output = $return = false;

        \exec("mklink /{$mode} \"{$link}\" \"{$target}\"", $output, $return);

        return $return;
    }

    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return bool
     */
    abstract public function has(string $path): bool;

    /**
     * Determine if the given path is a directory.
     *
     * @param string $dirname
     *
     * @return bool
     */
    abstract public function isDirectory(string $dirname): bool;

    /**
     * Determine whether the current environment is Windows based.
     *
     * @return bool
     *
     * @codeCoverageIgnore
     */
    protected function isWindows(): bool
    {
        return \mb_stripos(\PHP_OS, 'win') === 0;
    }

    /**
     * Transforms the path.
     *
     * @param string $path
     *
     * @return string
     */
    abstract protected function getTransformedPath(string $path): string;
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Session\Handler;

use Cake\Chronos\Chronos;
use Psr\Http\Message\ServerRequestInterface;
use Viserio\Component\Contract\Cookie\Cookie as CookieContract;
use Viserio\Component\Contract\Cookie\QueueingFactory as JarContract;

class CookieSessionHandler extends AbstractSessionHandler
{
    /**
     * The cookie jar instance.
     *
     * @var \Viserio\Component\Contract\Cookie\QueueingFactory
     */
    protected $cookie;

    /**
     * The request instance.
     *
     * @var \Psr\Http\Message\ServerRequestInterface
     */
    protected $request;

    /**
     * The number of seconds the session should be valid.
     *
     * @var int
     */
    protected $lifetime;

    /**
     * Create a new cookie driven handler instance.
     *
     * @param \Viserio\Component\Contract\Cookie\QueueingFactory $cookie
     * @param int                                                $lifetime The session lifetime in seconds
     */
    public function __construct(JarContract $cookie, int $lifetime)
    {
        $this->cookie   = $cookie;
        $this->lifetime = $lifetime;
    }

    /**
     * Set the request instance.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request
     */
    public function setRequest(ServerRequestInterface $request): void
    {
        $this->request = $request;
    }

    /**
     * {@inheritdoc}
     */
    public function close(): bool
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function gc($lifetime): bool
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function updateTimestamp($sessionId, $data): bool
    {
        $cookies = $this->cookie->getQueuedCookies();
        $cookie  = $cookies[$sessionId] ?? null;

        if ($cookie === null) {
            return false;
        }

        $this->cookie->queue($this->cookie->delete($sessionId));
        // @var CookieContract $cookie
        $this->cookie->queue(
            $cookie->withExpires(
                Chronos::now()->addSeconds($this->lifetime)->getTimestamp()
            )
        );

        return $this->cookie->hasQueued($sessionId);
    }

    /**
     * {@inheritdoc}
     */
    protected function doRead($sessionId): string
    {
        $cookies = $this->request->getCookieParams();

        if (! isset($cookies[$sessionId])) {
            return '';
        }

        $decoded = \json_decode(\base64_decode($cookies[$sessionId], true), true);

        if (\is_array($decoded) &&
            (isset($decoded['expires']) && Chronos::now()->getTimestamp() <= $decoded['expires'])
        ) {
            return $decoded['data'];
        }

        return '';
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite($sessionId, $data): bool
    {
        $this->cookie->queue(
            $sessionId,
            \base64_encode(\json_encode(
                [
                    'data'    => $data,
                    'expires' => Chronos::now()->addSeconds($this->lifetime)->getTimestamp(),
                ],
                \JSON_PRESERVE_ZERO_FRACTION
            )),
            $this->lifetime
        );

        return true;
    }

    /**
     * {@inheritdoc}
     */
    protected function doDestroy($sessionId): bool
    {
        $this->cookie->queue($this->cookie->delete($sessionId));

        return $this->cookie->hasQueued($sessionId);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Translation;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use Viserio\Component\Contract\Parser\Traits\ParserAwareTrait;
use Viserio\Component\Contract\Translation\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Translation\Exception\RuntimeException;
use Viserio\Component\Contract\Translation\MessageCatalogue as MessageCatalogueContract;
use Viserio\Component\Contract\Translation\MessageFormatter as MessageFormatterContract;
use Viserio\Component\Contract\Translation\TranslationManager as TranslationManagerContract;
use Viserio\Component\Contract\Translation\Translator as TranslatorContract;
use Viserio\Component\Translation\Traits\ValidateLocaleTrait;

class TranslationManager implements TranslationManagerContract, LoggerAwareInterface
{
    use ValidateLocaleTrait;
    use ParserAwareTrait;
    use LoggerAwareTrait;

    /**
     * MessageFormatter instance.
     *
     * @var \Viserio\Component\Contract\Translation\MessageFormatter
     */
    protected $formatter;

    /**
     * A string dictating the default language to translate into. (e.g. 'en').
     *
     * @var string
     */
    protected $locale = 'en';

    /**
     * Default fallback for all languages.
     *
     * @var null|\Viserio\Component\Contract\Translation\MessageCatalogue
     */
    protected $defaultFallback;

    /**
     * Fallbacks for special languages.
     *
     * @var array
     */
    protected $langFallback = [];

    /**
     * All directories to look for a file.
     *
     * @var array
     */
    protected $directories = [];

    /**
     * All added translations.
     *
     * @var array
     */
    protected $translations = [];

    /**
     * Create a new Translation instance.
     *
     * @param \Viserio\Component\Contract\Translation\MessageFormatter $formatter
     */
    public function __construct(MessageFormatterContract $formatter)
    {
        $this->formatter = $formatter;
        $this->logger    = new NullLogger();
    }

    /**
     * Gets the string dictating the default language.
     *
     * @return string
     */
    public function getLocale(): string
    {
        return $this->locale;
    }

    /**
     * Sets the string dictating the default language to translate into. (e.g. 'en').
     *
     * @param string $locale
     *
     * @return $this
     */
    public function setLocale(string $locale): self
    {
        self::assertValidLocale($locale);

        $this->locale = $locale;

        return $this;
    }

    /**
     * Get default fallback.
     *
     * @return null|\Viserio\Component\Contract\Translation\MessageCatalogue
     */
    public function getDefaultFallback(): ?MessageCatalogueContract
    {
        return $this->defaultFallback;
    }

    /**
     * Set default fallback for all languages.
     *
     * @param \Viserio\Component\Contract\Translation\MessageCatalogue $fallback
     *
     * @return $this
     */
    public function setDefaultFallback(MessageCatalogueContract $fallback): self
    {
        $this->defaultFallback = $fallback;

        return $this;
    }

    /**
     * Get directories.
     *
     * @return array
     */
    public function getDirectories(): array
    {
        return $this->directories;
    }

    /**
     * Set directories.
     *
     * @param array $directories
     *
     * @return $this
     */
    public function setDirectories(array $directories): self
    {
        foreach ($directories as $directory) {
            $this->addDirectory($directory);
        }

        return $this;
    }

    /**
     * Add directory.
     *
     * @param string $directory
     *
     * @return $this
     */
    public function addDirectory(string $directory): self
    {
        if (! \in_array($directory, $this->directories, true)) {
            $this->directories[] = $directory;
        }

        return $this;
    }

    /**
     * Import a language from file.
     *
     * @param string $file
     *
     * @throws \Viserio\Component\Contract\Translation\Exception\InvalidArgumentException
     *
     * @return $this
     */
    public function import(string $file): self
    {
        $loader = $this->getLoader();
        $loader->setDirectories($this->directories);

        $langFile = $loader->load($file);

        if (! isset($langFile['lang'])) {
            throw new InvalidArgumentException(\sprintf('File [%s] cant be imported. Key for language is missing.', $file));
        }

        $this->addMessageCatalogue(new MessageCatalogue($langFile['lang'], $langFile));

        return $this;
    }

    /**
     * Add message catalogue.
     *
     * @param \Viserio\Component\Contract\Translation\MessageCatalogue $messageCatalogue
     *
     * @return $this
     */
    public function addMessageCatalogue(MessageCatalogueContract $messageCatalogue): self
    {
        $locale = $messageCatalogue->getLocale();

        if ($fallback = $this->getLanguageFallback($messageCatalogue->getLocale())) {
            $messageCatalogue->addFallbackCatalogue($fallback);
        } elseif ($this->defaultFallback !== null) {
            $messageCatalogue->addFallbackCatalogue($this->defaultFallback);
        }

        $translation = new Translator($messageCatalogue, $this->formatter);

        $translation->setLogger($this->logger);

        $this->translations[$locale] = $translation;

        return $this;
    }

    /**
     * Set fallback for a language.
     *
     * @param string                                                   $lang
     * @param \Viserio\Component\Contract\Translation\MessageCatalogue $fallback
     *
     * @throws \RuntimeException
     *
     * @return $this
     */
    public function setLanguageFallback(string $lang, MessageCatalogueContract $fallback): self
    {
        $this->langFallback[$lang] = $fallback;

        return $this;
    }

    /**
     * Get fallback for a language.
     *
     * @param string $lang
     *
     * @return null|\Viserio\Component\Contract\Translation\MessageCatalogue
     */
    public function getLanguageFallback(string $lang): ?MessageCatalogueContract
    {
        if (isset($this->langFallback[$lang])) {
            return $this->langFallback[$lang];
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getTranslator(string $locale = null): TranslatorContract
    {
        $lang = $locale ?? $this->locale;

        if (isset($this->translations[$lang])) {
            return $this->translations[$lang];
        }

        throw new RuntimeException(\sprintf('Translator for [%s] doesn\'t exist.', $lang));
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Pagination;

use Narrowspark\Collection\Collection;
use Psr\Http\Message\ServerRequestInterface;
use Viserio\Component\Contract\Pagination\Adapter as AdapterContract;
use Viserio\Component\Contract\Pagination\Presenter as PresenterContract;
use Viserio\Component\Contract\View\Traits\ViewAwareTrait;
use Viserio\Component\Pagination\Presenter\Bootstrap4;
use Viserio\Component\Pagination\Presenter\Foundation6;
use Viserio\Component\Pagination\Presenter\SemanticUi;
use Viserio\Component\Pagination\Presenter\SimplePagination;

class Paginator extends AbstractPaginator
{
    use ViewAwareTrait;

    /**
     * All pagination presenters.
     *
     * @var array
     */
    protected $presenters = [
        'bootstrap4'  => Bootstrap4::class,
        'foundation6' => Foundation6::class,
        'sematicui'   => SemanticUi::class,
        'simple'      => SimplePagination::class,
    ];

    /**
     * The default pagination presenter.
     *
     * @var string
     */
    protected $presenter = 'simple';

    /**
     * Determine if there are more items in the data source.
     *
     * @return bool
     */
    protected $hasMore = false;

    /**
     * Create a new paginator.
     *
     * @param \Viserio\Component\Contract\Pagination\Adapter $adapter
     * @param \Psr\Http\Message\ServerRequestInterface       $request
     */
    public function __construct(AdapterContract $adapter, ServerRequestInterface $request)
    {
        $this->request = $request;
        $this->setPath($this->request->getUri()->getPath());

        $this->items            = new Collection($adapter->getItems());
        $this->itemCountPerPage = $adapter->getItemsPerPage();
        $this->currentPage      = $this->getCurrentPage();

        $this->checkForMorePages();
    }

    /**
     * Add a new presenter.
     *
     * @param string                                           $key
     * @param \Viserio\Component\Contract\Pagination\Presenter $presenter
     *
     * @return $this
     */
    public function addPresenter(string $key, PresenterContract $presenter): self
    {
        $this->presenter[$key] = $presenter;

        return $this;
    }

    /**
     * Set a default presenter.
     *
     * @param string $presenter
     *
     * @return $this
     */
    public function setDefaultPresenter(string $presenter): self
    {
        $this->presenter = $presenter;

        return $this;
    }

    /**
     * Get the default presenter.
     *
     * @return string
     */
    public function getDefaultPresenter(): string
    {
        return $this->presenter;
    }

    /**
     * {@inheritdoc}
     */
    public function getNextPageUrl(): ?string
    {
        if ($this->hasMorePages()) {
            return $this->getUrl($this->getCurrentPage() + 1);
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function render(?string $view = null): string
    {
        if ($this->viewFactory !== null && ! isset($this->presenters[$view])) {
            return (string) $this->viewFactory->create($view, ['paginator' => $this]);
        }

        if (isset($this->presenters[$view])) {
            return (new $this->presenters[$view]($this))->render();
        }

        return (new $this->presenters[$this->getDefaultPresenter()]($this))->render();
    }

    /**
     * {@inheritdoc}
     */
    public function toArray(): array
    {
        return [
            'per_page'      => $this->getItemsPerPage(),
            'current_page'  => $this->getCurrentPage(),
            'next_page_url' => $this->getNextPageUrl(),
            'prev_page_url' => $this->getPreviousPageUrl(),
            'from'          => $this->getFirstItem(),
            'to'            => $this->getLastItem(),
            'data'          => $this->items->toArray(),
            'path'          => $this->path,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }

    /**
     * {@inheritdoc}
     */
    public function toJson(int $options = 0): string
    {
        return \json_encode($this->jsonSerialize(), $options);
    }

    /**
     * Manually indicate that the paginator does have more pages.
     *
     * @param bool $value
     *
     * @return $this
     *
     * @codeCoverageIgnore
     */
    public function hasMorePagesWhen(bool $value = true): self
    {
        $this->hasMore = $value;

        return $this;
    }

    /**
     * Determine if there are more items in the data source.
     *
     * @return bool
     */
    public function hasMorePages(): bool
    {
        return $this->hasMore;
    }

    /**
     * {@inheritdoc}
     */
    public function getCurrentPage(): int
    {
        $currentPage = $this->resolveCurrentPage();

        return $this->isValidPageNumber($currentPage) ? $currentPage : 1;
    }

    /**
     * Check for more pages. The last item will be sliced off.
     *
     * @return void
     */
    protected function checkForMorePages(): void
    {
        $this->hasMore = \count($this->items) > $this->itemCountPerPage;

        $this->items = $this->items->slice(0, $this->itemCountPerPage);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Translation;

use Viserio\Component\Contract\Translation\Exception\LogicException;
use Viserio\Component\Contract\Translation\MessageCatalogue as MessageCatalogueContract;
use Viserio\Component\Translation\Traits\ValidateLocaleTrait;

class MessageCatalogue implements MessageCatalogueContract
{
    use ValidateLocaleTrait;

    /**
     * List of messages.
     *
     * @var array
     */
    protected $messages = [];

    /**
     * Message catalogue instance.
     *
     * @var \Viserio\Component\Contract\Translation\MessageCatalogue
     */
    protected $fallbackCatalogue;

    /**
     * A string dictating the default language to translate into. (e.g. 'en').
     *
     * @var string
     */
    protected $locale = 'en';

    /**
     * A parent instance of MessageCatalogue.
     *
     * @var \Viserio\Component\Contract\Translation\MessageCatalogue
     */
    protected $parent;

    /**
     * Constructor.
     *
     * @param string $locale   The locale
     * @param array  $messages An array of messages classified by domain
     */
    public function __construct(string $locale, array $messages = [])
    {
        self::assertValidLocale($locale);

        $this->locale   = $locale;
        $this->messages = $messages;
    }

    /**
     * {@inheritdoc}
     */
    public function getFallbackCatalogue(): ?MessageCatalogueContract
    {
        return $this->fallbackCatalogue;
    }

    /**
     * {@inheritdoc}
     */
    public function getLocale(): string
    {
        return $this->locale;
    }

    /**
     * {@inheritdoc}
     */
    public function setParent(MessageCatalogueContract $parent): MessageCatalogueContract
    {
        $this->parent = $parent;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getDomains(): array
    {
        return \array_keys($this->messages);
    }

    /**
     * {@inheritdoc}
     */
    public function getAll(string $domain = null): array
    {
        if ($domain === null) {
            return $this->messages;
        }

        return $this->messages[$domain] ?? [];
    }

    /**
     * {@inheritdoc}
     */
    public function set(string $id, string $translation, string $domain = 'messages'): void
    {
        $this->add([$id => $translation], $domain);
    }

    /**
     * {@inheritdoc}
     */
    public function has(string $id, string $domain = 'messages'): bool
    {
        if (isset($this->messages[$domain][$id])) {
            return true;
        }

        if ($this->fallbackCatalogue !== null) {
            return $this->fallbackCatalogue->has($id, $domain);
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function defines(string $id, string $domain = 'messages'): bool
    {
        return isset($this->messages[$domain][$id]);
    }

    /**
     * {@inheritdoc}
     */
    public function get(string $id, string $domain = 'messages'): string
    {
        if (isset($this->messages[$domain][$id])) {
            return $this->messages[$domain][$id];
        }

        if ($this->fallbackCatalogue !== null) {
            return $this->fallbackCatalogue->get($id, $domain);
        }

        return $id;
    }

    /**
     * {@inheritdoc}
     */
    public function replace(array $messages, string $domain = 'messages'): void
    {
        $this->messages[$domain] = [];
        $this->add($messages, $domain);
    }

    /**
     * {@inheritdoc}
     */
    public function remove(string $messages, string $domain = 'messages'): void
    {
        unset($this->messages[$domain][$messages]);
    }

    /**
     * {@inheritdoc}
     */
    public function add(array $messages, string $domain = 'messages'): void
    {
        if (! isset($this->messages[$domain])) {
            $this->messages[$domain] = $messages;
        } else {
            $this->messages[$domain] = \array_replace($this->messages[$domain], $messages);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function addCatalogue(MessageCatalogueContract $catalogue): void
    {
        if ($catalogue->getLocale() !== $this->locale) {
            throw new LogicException(\sprintf(
                'Cannot add a catalogue for locale [%s] as the current locale for this catalogue is [%s].',
                $catalogue->getLocale(),
                $this->locale
            ));
        }

        foreach ($catalogue->getAll() as $domain => $messages) {
            $this->add($messages, $domain);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function addFallbackCatalogue(MessageCatalogueContract $catalogue): void
    {
        // detect circular references
        $circular = $this;

        do {
            if ($circular->getLocale() === $catalogue->getLocale()) {
                throw new LogicException(\sprintf(
                    'Circular reference detected when adding a fallback catalogue for locale [%s].',
                    $catalogue->getLocale()
                ));
            }
        } while ($circular = $circular->parent);

        $catalogue->setParent($this);

        $this->fallbackCatalogue = $catalogue;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\WebServer;

use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Contract\OptionsResolver\Exception\InvalidArgumentException as OptionsResolverInvalidArgumentException;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresConfig as RequiresConfigContract;
use Viserio\Component\Contract\OptionsResolver\RequiresValidatedConfig as RequiresValidatedConfigContract;
use Viserio\Component\Contract\WebServer\Exception\InvalidArgumentException;
use Viserio\Component\Contract\WebServer\Exception\RuntimeException;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

final class WebServerConfig implements RequiresConfigContract, ProvidesDefaultOptionsContract, RequiresValidatedConfigContract
{
    use OptionsResolverTrait;

    /**
     * Resolved options.
     *
     * @var array
     */
    private $resolvedOptions;

    /**
     * Create a new WebServerConfig instance.
     *
     * @param string                                             $documentRoot
     * @param string                                             $environment
     * @param \Viserio\Component\Console\Command\AbstractCommand $command
     */
    public function __construct(string $documentRoot, string $environment, AbstractCommand $command)
    {
        $config = [
            'disable-xdebug'  => ! \filter_var(\ini_get('xdebug.profiler_enable_trigger'), \FILTER_VALIDATE_BOOLEAN),
            'pidfile'         => null,
            'document_root'   => $documentRoot,
            'env'             => $environment,
        ];

        if ($command->hasOption('host')) {
            $config['host'] = $command->option('host');
        }

        if ($command->hasOption('port')) {
            $config['port'] = $command->option('port');
        }

        if ($command->hasOption('router')) {
            $config['router'] = $command->option('router');
        } else {
            $config['router'] = __DIR__ . \DIRECTORY_SEPARATOR . 'Resources' . \DIRECTORY_SEPARATOR . 'router.php';
        }

        if ($command->hasOption('pidfile')) {
            $config['pidfile'] = $command->option('pidfile');
        }

        if ($command->hasOption('disable-xdebug')) {
            $config['disable-xdebug'] = true;
        }

        $this->resolvedOptions = self::findHostnameAndPort(self::resolveOptions($config));

        $_ENV['APP_FRONT_CONTROLLER'] = self::findFrontController(
            $this->resolvedOptions['document_root'],
            $this->resolvedOptions['env']
        );
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        return [
            'host' => null,
            'port' => null,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function getOptionValidators(): array
    {
        return [
            'document_root' => static function ($value): void {
                if (! \is_dir($value)) {
                    throw new OptionsResolverInvalidArgumentException(\sprintf('The document root directory [%s] does not exist.', $value));
                }
            },
            'router' => static function ($value): void {
                if (! \is_string($value)) {
                    throw OptionsResolverInvalidArgumentException::invalidType('router', $value, ['string'], self::class);
                }

                if (\realpath($value) === false) {
                    throw new OptionsResolverInvalidArgumentException(\sprintf('Router script [%s] does not exist.', $value));
                }
            },
            'host'           => ['string', 'null'],
            'port'           => ['int', 'string', 'null'],
            'disable-xdebug' => ['bool'],
        ];
    }

    /**
     * Return the path to the document folder, where you can find the index.php.
     *
     * @return string
     */
    public function getDocumentRoot(): string
    {
        return $this->resolvedOptions['document_root'];
    }

    /**
     * Return the environment.
     *
     * @return string
     */
    public function getEnv(): string
    {
        return $this->resolvedOptions['env'];
    }

    /**
     * Returns the router file.
     *
     * @return string
     */
    public function getRouter(): string
    {
        return $this->resolvedOptions['router'];
    }

    /**
     * Returns the given host name or the default 127.0.0.1.
     *
     * @return string
     */
    public function getHostname(): string
    {
        return $this->resolvedOptions['host'];
    }

    /**
     * Returns the given port or a found free port between 8000 and 8100.
     *
     * @return string
     */
    public function getPort(): string
    {
        return (string) $this->resolvedOptions['port'];
    }

    /**
     * Return the full address of the hostname:port.
     *
     * @return string
     */
    public function getAddress(): string
    {
        return $this->resolvedOptions['address'];
    }

    /**
     * Check if xdebug should be used.
     *
     * @return bool
     */
    public function hasXdebug(): bool
    {
        return $this->resolvedOptions['disable-xdebug'] === false;
    }

    /**
     * Return a path to the pid file it it was given.
     *
     * @return null|string
     */
    public function getPidFile(): ?string
    {
        return $this->resolvedOptions['pidfile'] ?? null;
    }

    /**
     * Contains resolved hostname if available.
     *
     * @return null|string
     */
    public function getDisplayAddress(): ?string
    {
        if ('0.0.0.0' !== $this->getHostname()) {
            return null;
        }

        if (false === $localHostname = \gethostname()) {
            return null;
        }

        return \gethostbyname($localHostname) . ':' . $this->getPort();
    }

    /**
     * Finds the front controller in root path.
     *
     * @param string $documentRoot
     * @param string $env
     *
     * @throws \Viserio\Component\Contract\WebServer\Exception\InvalidArgumentException
     *
     * @return string
     */
    private static function findFrontController(string $documentRoot, string $env): string
    {
        $fileNames = ['index_' . $env . '.php', 'index.php'];

        foreach ($fileNames as $fileName) {
            if (\file_exists($documentRoot . \DIRECTORY_SEPARATOR . $fileName)) {
                return $fileName;
            }
        }

        throw new InvalidArgumentException(
            \sprintf(
                'Unable to find the front controller under [%s] (none of these files exist: [%s]).',
                $documentRoot,
                \implode(', ', $fileNames)
            )
        );
    }

    /**
     * Finds a host and port.
     *
     * @param array $config
     *
     * @throws \Viserio\Component\Contract\WebServer\Exception\InvalidArgumentException
     *
     * @return array
     */
    private static function findHostnameAndPort(array $config): array
    {
        if ($config['host'] === null) {
            $config['host'] = '127.0.0.1';
            $config['port'] = self::findBestPort($config['host']);
        } elseif (isset($config['host'], $config['port']) && $config['port'] !== null && $config['host'] === '*') {
            $config['host'] = '0.0.0.0';
        } elseif ($config['port'] === null) {
            $config['port'] = self::findBestPort($config['host']);
        }

        if (! \ctype_digit((string) $config['port'])) {
            throw new InvalidArgumentException(\sprintf('Port [%s] is not valid.', (string) $config['port']));
        }

        $config['address'] = $config['host'] . ':' . $config['port'];

        return $config;
    }

    /**
     * Searching for the port between 8000 and 8100.
     *
     * @param string $host
     *
     * @return string
     */
    private static function findBestPort(string $host): string
    {
        $port = 8000;

        while (false !== $fp = @\fsockopen($host, $port, $errno, $errstr, 1)) {
            \fclose($fp);

            if ($port++ >= 8100) {
                throw new RuntimeException('Unable to find a port available to run the web server.');
            }
        }

        return (string) $port;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\HttpFactory;

use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Viserio\Component\Http\Util;

final class StreamFactory implements StreamFactoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function createStream(string $content = ''): StreamInterface
    {
        $stream = \fopen('php://memory', 'r+b');

        \fwrite($stream, $content);
        \fseek($stream, 0);

        return Util::createStreamFor($stream);
    }

    /**
     * {@inheritdoc}
     */
    public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
    {
        return Util::createStreamFor(Util::tryFopen($filename, $mode));
    }

    /**
     * {@inheritdoc}
     */
    public function createStreamFromResource($resource): StreamInterface
    {
        return Util::createStreamFor($resource);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\HttpFactory;

use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Viserio\Component\Contract\Http\Exception\InvalidArgumentException;
use Viserio\Component\Http\ServerRequest;
use Viserio\Component\Http\Stream\LazyOpenStream;

class ServerRequestFactory implements ServerRequestFactoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
    {
        if ($method === '') {
            if (! empty($serverParams['REQUEST_METHOD'])) {
                $method = $serverParams['REQUEST_METHOD'];
            } else {
                throw new InvalidArgumentException('Cannot determine HTTP method.');
            }
        }

        return new ServerRequest(
            $uri,
            $method,
            [],
            new LazyOpenStream('php://temp', 'r+'),
            '1.1',
            $serverParams
        );
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Profiler\DataCollector\Bridge\Cache\Traits;

use Psr\Cache\CacheItemInterface;

trait TraceableCacheItemDecoratorTrait
{
    /**
     * {@inheritdoc}
     */
    public function getItem($key)
    {
        $event = $this->start(__FUNCTION__);

        try {
            $item = $this->pool->getItem($key);
        } finally {
            $event->end = \microtime(true);
        }

        if ($event->result[$key] = $item->isHit()) {
            $event->hits++;
        } else {
            $event->misses++;
        }

        return $item;
    }

    /**
     * {@inheritdoc}
     */
    public function hasItem($key)
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result[$key] = $this->pool->hasItem($key);
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function deleteItem($key)
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result[$key] = $this->pool->deleteItem($key);
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function save(CacheItemInterface $item)
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result[$item->getKey()] = $this->pool->save($item);
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function saveDeferred(CacheItemInterface $item)
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result[$item->getKey()] = $this->pool->saveDeferred($item);
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getItems(array $keys = []): \Generator
    {
        $event = $this->start(__FUNCTION__);

        try {
            $result = $this->pool->getItems($keys);
        } finally {
            $event->end = \microtime(true);
        }

        $f = static function () use ($result, $event) {
            $event->result = [];

            foreach ($result as $key => $item) {
                if ($event->result[$key] = $item->isHit()) {
                    $event->hits++;
                } else {
                    $event->misses++;
                }

                yield $key => $item;
            }
        };

        return $f();
    }

    /**
     * {@inheritdoc}
     */
    public function deleteItems(array $keys)
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result = $this->pool->deleteItems($keys);
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function commit()
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result = $this->pool->commit();
        } finally {
            $event->end = \microtime(true);
        }
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Profiler;

use Viserio\Component\Contract\Profiler\PanelAware as PanelAwareContract;
use Viserio\Component\Contract\Profiler\TooltipAware as TooltipAwareContract;
use Viserio\Component\Contract\Support\Renderable as RenderableContract;

class TemplateManager implements RenderableContract
{
    /**
     * All registered data collectors.
     *
     * @var array
     */
    protected $collectors = [];

    /**
     * Path for the Profiler template.
     *
     * @var string
     */
    private $templatePath;

    /**
     * List of icons.
     *
     * @var array
     */
    private $icons;

    /**
     * Request token.
     *
     * @var string
     */
    private $token;

    /**
     * Create a new template manager instance.
     *
     * @param array  $collectors
     * @param string $templatePath
     * @param string $token
     * @param array  $icons
     */
    public function __construct(array $collectors, string $templatePath, string $token, array $icons = [])
    {
        $this->collectors   = $collectors;
        $this->templatePath = $templatePath;
        $this->token        = $token;
        $this->icons        = $icons;
    }

    /**
     * Escapes a string for output in an HTML document.
     *
     * @param string $raw
     *
     * @return string
     */
    public static function escape(string $raw): string
    {
        $flags = \ENT_QUOTES;

        if (\defined('ENT_SUBSTITUTE')) {
            $flags |= \ENT_SUBSTITUTE;
        }

        $raw = \str_replace(\chr(9), '    ', $raw);

        return \htmlspecialchars($raw, $flags);
    }

    /**
     * {@inheritdoc}
     */
    public function render(): string
    {
        $obLevel = \ob_get_level();

        \ob_start();

        $data = \array_merge(
            $this->getSortedData(),
            [
                'token' => $this->token,
            ]
        );

        \extract(
            $data,
            \EXTR_PREFIX_SAME,
            'viserio'
        );

        require $this->templatePath;

        // @codeCoverageIgnoreStart
        // Return temporary output buffer content, destroy output buffer
        return \ltrim(\ob_get_clean());
        // @codeCoverageIgnoreEnd
    }

    /**
     * Sort all datas from collectors.
     *
     * @return array
     */
    public function getSortedData(): array
    {
        $data = [
            'menus'  => [],
            'panels' => [],
            'icons'  => $this->icons,
        ];

        foreach ($this->collectors as $name => $collector) {
            $collector = $collector['collector'];

            if ($collector instanceof TooltipAwareContract) {
                $data['menus'][$collector->getName()] = [
                    'menu'     => $collector->getMenu(),
                    'tooltip'  => $collector->getTooltip(),
                    'position' => $collector->getMenuPosition(),
                ];
            } else {
                $data['menus'][$collector->getName()] = [
                    'menu'     => $collector->getMenu(),
                    'position' => $collector->getMenuPosition(),
                ];
            }

            if ($collector instanceof PanelAwareContract) {
                $class = '';
                $panel = $collector->getPanel();

                // @codeCoverageIgnoreStart
                if (\mb_strpos($panel, '<div class="profiler-tabs') !== false) {
                    $class = ' profiler-body-has-tabs';
                } elseif (\mb_strpos($panel, '<select class="content-selector"') !== false) {
                    $class = ' profiler-body-has-selector';
                } elseif (\mb_strpos($panel, 'getName()] = [
                    'content' => $panel,
                    'class'   => $class,
                ];
            }
        }

        return $data;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Http\Stream;

use Narrowspark\MimeType\MimeType;
use Psr\Http\Message\StreamInterface;
use Viserio\Component\Contract\Http\Exception\InvalidArgumentException;
use Viserio\Component\Http\Util;

/**
 * Stream that when read returns bytes for a streaming multipart or
 * multipart/form-data stream.
 */
class MultipartStream extends AbstractStreamDecorator
{
    private $boundary;

    /**
     * @param array  $elements array of associative arrays, each containing a
     *                         required "name" key mapping to the form field,
     *                         name, a required "contents" key mapping to a
     *                         StreamInterface/resource/string, an optional
     *                         "headers" associative array of custom headers,
     *                         and an optional "filename" key mapping to a
     *                         string to send as the filename in the part
     * @param string $boundary You can optionally provide a specific boundary
     *
     * @throws \Viserio\Component\Contract\Http\Exception\InvalidArgumentException
     */
    public function __construct(array $elements = [], $boundary = null)
    {
        $this->boundary = $boundary ?: \sha1(\uniqid('', true));

        parent::__construct($this->createAppendStream($elements));
    }

    /**
     * Get the boundary.
     *
     * @return string
     */
    public function getBoundary(): string
    {
        return $this->boundary;
    }

    /**
     * {@inheritdoc}
     */
    public function isWritable(): bool
    {
        return false;
    }

    /**
     * Create the aggregate stream that will be used to upload the POST data.
     *
     * @param array $elements
     *
     * @return \Psr\Http\Message\StreamInterface
     */
    private function createAppendStream(array $elements): StreamInterface
    {
        $stream = new AppendStream();

        foreach ($elements as $element) {
            $this->addElement($stream, $element);
        }

        // Add the trailing boundary with CRLF
        $stream->addStream(Util::createStreamFor("--{$this->boundary}--\r\n"));

        return $stream;
    }

    /**
     * Get the headers needed before transferring the content of a POST file.
     *
     * @param array $headers
     *
     * @return string
     */
    private function getHeaders(array $headers): string
    {
        $str = '';

        foreach ($headers as $key => $value) {
            $str .= "{$key}: {$value}\r\n";
        }

        return "--{$this->boundary}\r\n" . \trim($str) . "\r\n\r\n";
    }

    /**
     * @param AppendStream $stream
     * @param array        $element
     *
     * @return void
     */
    private function addElement(AppendStream $stream, array $element): void
    {
        foreach (['contents', 'name'] as $key) {
            if (! \array_key_exists($key, $element)) {
                throw new InvalidArgumentException(\sprintf('A [%s] key is required', $key));
            }
        }

        $element['contents'] = Util::createStreamFor($element['contents']);

        if (empty($element['filename'])) {
            $uri = $element['contents']->getMetadata('uri');

            if (\substr($uri, 0, 6) !== 'php://') {
                $element['filename'] = $uri;
            }
        }

        [$body, $headers] = $this->createElement(
            $element['name'],
            $element['contents'],
            $element['filename'] ?? null,
            $element['headers'] ?? []
        );

        $stream->addStream(Util::createStreamFor($this->getHeaders($headers)));
        $stream->addStream($body);
        $stream->addStream(Util::createStreamFor("\r\n"));
    }

    /**
     * @param string                            $name
     * @param \Psr\Http\Message\StreamInterface $stream
     * @param null|string                       $filename
     * @param array                             $headers
     *
     * @return array
     */
    private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
    {
        // Set a default content-disposition header if one was no provided
        $disposition = $this->getHeader($headers, 'content-disposition');

        if (! $disposition) {
            $headers['Content-Disposition'] = ($filename === '0' || $filename)
                ? \sprintf(
                    'form-data; name="%s"; filename="%s"',
                    $name,
                    self::basename($filename)
                )
                : "form-data; name=\"{$name}\"";
        }

        // Set a default content-length header if one was no provided
        $length = $this->getHeader($headers, 'content-length');

        if (! $length) {
            if ($length = $stream->getSize()) {
                $headers['Content-Length'] = (string) $length;
            }
        }

        // Set a default Content-Type if one was not supplied
        $type = $this->getHeader($headers, 'content-type');

        if (! $type && ($filename === '0' || $filename)) {
            if ($type = MimeType::guess($filename)) {
                $headers['Content-Type'] = $type;
            }
        }

        return [$stream, $headers];
    }

    private function getHeader(array $headers, $key)
    {
        $lowercaseHeader = \strtolower($key);

        foreach ($headers as $k => $v) {
            if (\strtolower($k) === $lowercaseHeader) {
                return $v;
            }
        }

        return null;
    }

    /**
     * Gets the filename from a given path.
     *
     * PHP's basename() does not properly support streams or filenames beginning with a non-US-ASCII character.
     *
     * @author Drupal 8.2
     *
     * @param string $path
     *
     * @return string
     */
    private static function basename($path): string
    {
        $separators = '/';

        if (\DIRECTORY_SEPARATOR !== '/') {
            // For Windows OS add special separator.
            $separators .= \DIRECTORY_SEPARATOR;
        }

        // Remove right-most slashes when $path points to directory.
        $path = \rtrim($path, $separators);

        // Returns the trailing part of the $path starting after one of the directory separators.
        return \preg_match('@[^' . \preg_quote($separators, '@') . ']+$@', $path, $matches) ? $matches[0] : '';
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Translation\Extractor;

use ArrayIterator;
use Iterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Viserio\Component\Contract\Translation\Exception\InvalidArgumentException;
use Viserio\Component\Translation\Extractor\PhpParser\ScalarString;

class PhpExtractor extends AbstractFileExtractor
{
    public const MESSAGE_TOKEN          = 300;
    public const METHOD_ARGUMENTS_TOKEN = 1000;
    public const DOMAIN_TOKEN           = 1001;

    /**
     * Default domain for found messages.
     *
     * @var string
     */
    protected $defaultDomain = 'messages';

    /**
     * The sequence that captures translation messages.
     *
     * @var array
     */
    protected $sequences = [
        [
            '->',
            'trans',
            '(',
            self::MESSAGE_TOKEN,
            ',',
            self::METHOD_ARGUMENTS_TOKEN,
            ',',
            self::DOMAIN_TOKEN,
        ],
        [
            '->',
            'trans',
            '(',
            self::MESSAGE_TOKEN,
        ],
    ];

    /**
     * {@inheritdoc}
     */
    public function extract($resource): array
    {
        if (! \is_string($resource) && ! \is_array($resource)) {
            throw new InvalidArgumentException(\sprintf(
                'The resource parameter must be of type string or array, [%s] given.',
                \is_object($resource) ? \get_class($resource) : \gettype($resource)
            ));
        }

        $messages = [];
        $files    = $this->extractFiles($resource);

        foreach ($files as $file) {
            $tokens   = \token_get_all(\file_get_contents($file));
            $messages = \array_merge($messages, $this->parseTokens($tokens));

            // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
            \gc_mem_caches();
        }

        return $messages;
    }

    /**
     * Normalizes a token.
     *
     * @param mixed $token
     *
     * @return string
     */
    protected function normalizeToken($token): string
    {
        if ($token !== 'b"' && isset($token[1])) {
            return $token[1];
        }

        return $token;
    }

    /**
     * Extracts trans message from PHP tokens.
     *
     * @param array $tokens
     *
     * @return array
     */
    protected function parseTokens(array $tokens): array
    {
        $tokenIterator = new ArrayIterator($tokens);
        $messages      = [];

        for ($key = 0; $key < $tokenIterator->count(); $key++) {
            foreach ($this->sequences as $sequence) {
                $message = '';
                $domain  = $this->defaultDomain;
                $tokenIterator->seek($key);

                foreach ($sequence as $sequenceKey => $item) {
                    $this->seekToNextRelevantToken($tokenIterator);

                    if ($this->normalizeToken($tokenIterator->current()) === $item) {
                        $tokenIterator->next();

                        continue;
                    }

                    if (self::MESSAGE_TOKEN === $item) {
                        $message = $this->getValue($tokenIterator);

                        if (\count($sequence) === ($sequenceKey + 1)) {
                            break;
                        }
                    } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) {
                        $this->skipMethodArgument($tokenIterator);
                    } elseif (self::DOMAIN_TOKEN === $item) {
                        $domain = $this->getValue($tokenIterator);

                        break;
                    } else {
                        break;
                    }
                }

                if ($message) {
                    $messages[$domain][\trim($message)] = $this->prefix . \trim($message);

                    break;
                }
            }
        }

        return $messages;
    }

    /**
     * @param string $file
     *
     * @throws \InvalidArgumentException
     *
     * @return bool
     */
    protected function canBeExtracted($file): bool
    {
        return $this->isFile($file) && $this->isPhpFile($file);
    }

    /**
     * @param array|string $directory
     *
     * @return array
     */
    protected function extractFromDirectory($directory): array
    {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        $files    = [];

        foreach ($iterator as $file) {
            if ($this->isPhpFile($file->getPathname())) {
                $files[] = $file->getPathname();
            }
        }

        return $files;
    }

    /**
     * Seeks to a non-whitespace token.
     *
     * @param \Iterator $tokenIterator
     *
     * @return void
     */
    private function seekToNextRelevantToken(Iterator $tokenIterator): void
    {
        for (; $tokenIterator->valid(); $tokenIterator->next()) {
            $token = $tokenIterator->current();

            if ($token[0] !== \T_WHITESPACE) {
                break;
            }
        }
    }

    /**
     * @param \Iterator $tokenIterator
     *
     * @return void
     */
    private function skipMethodArgument(Iterator $tokenIterator): void
    {
        $openBraces = 0;

        for (; $tokenIterator->valid(); $tokenIterator->next()) {
            $token = $tokenIterator->current();

            if ($token[0] === '[' || $token[0] === '(') {
                $openBraces++;
            }

            if ($token[0] === ']' || $token[0] === ')') {
                $openBraces--;
            }

            if (($openBraces === 0 && $token[0] === ',') || ($openBraces === -1 && $token[0] === ')')) {
                break;
            }
        }
    }

    /**
     * Extracts the message from the iterator while the tokens
     * match allowed message tokens.
     *
     * @param \Iterator $tokenIterator
     *
     * @return string
     */
    private function getValue(Iterator $tokenIterator): string
    {
        $message  = '';
        $docToken = '';

        for (; $tokenIterator->valid(); $tokenIterator->next()) {
            $t = $tokenIterator->current();

            if (! isset($t[1])) {
                break;
            }

            switch ($t[0]) {
                case \T_START_HEREDOC:
                    $docToken = $t[1];

                    break;
                case \T_ENCAPSED_AND_WHITESPACE:
                case \T_CONSTANT_ENCAPSED_STRING:
                    $message .= $t[1];

                    break;
                case \T_END_HEREDOC:
                    return ScalarString::parseDocString($docToken, $message);

                default:
                    break 2;
            }
        }

        if ($message) {
            $message = ScalarString::parse($message);
        }

        return $message;
    }

    /**
     * Check if file is a php file.
     *
     * @param string $file
     *
     * @return bool
     */
    private function isPhpFile(string $file): bool
    {
        return \pathinfo($file, \PATHINFO_EXTENSION) === 'php';
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Parser\Provider;

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\Yaml\Yaml;
use Viserio\Component\Console\Application;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Parser\Command\XliffLintCommand;
use Viserio\Component\Parser\Command\YamlLintCommand;

class ConsoleCommandsServiceProvider implements
    ServiceProviderInterface,
    RequiresComponentConfigContract,
    ProvidesDefaultOptionsContract
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [
            Application::class => [self::class, 'extendConsole'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDimensions(): array
    {
        return ['viserio', 'console'];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        return [
            'lazily_commands' => [
                'lint:xliff' => XliffLintCommand::class,
                'lint:yaml'  => YamlLintCommand::class,
            ],
        ];
    }

    /**
     * Extend viserio console with commands.
     *
     * @param \Psr\Container\ContainerInterface           $container
     * @param null|\Viserio\Component\Console\Application $console
     *
     * @return null|\Viserio\Component\Console\Application
     */
    public static function extendConsole(ContainerInterface $container, ?Application $console = null): ?Application
    {
        if ($console !== null) {
            $console->add(new XliffLintCommand());
        }

        if (\class_exists(Yaml::class)) {
            $console->add(new YamlLintCommand());
        }

        return $console;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Profiler\Provider;

use Interop\Container\ServiceProviderInterface;
use PDO;
use Psr\Container\ContainerInterface;
use Viserio\Component\Contract\Profiler\Profiler as ProfilerContract;
use Viserio\Component\Profiler\DataCollector\Bridge\PDO\PDODataCollector;
use Viserio\Component\Profiler\DataCollector\Bridge\PDO\TraceablePDODecorater;

class ProfilerPDOBridgeServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [
            PDO::class                   => [self::class, 'createTraceablePDODecorator'],
            TraceablePDODecorater::class => static function (ContainerInterface $container) {
                return $container->get(PDO::class);
            },
            ProfilerContract::class => [self::class, 'extendProfiler'],
        ];
    }

    /**
     * Extend PDO with our TraceablePDODecorater.
     *
     * @param \Psr\Container\ContainerInterface $container
     * @param null|\PDO                         $pdo
     *
     * @return null|\Viserio\Component\Profiler\DataCollector\Bridge\PDO\TraceablePDODecorater
     */
    public static function createTraceablePDODecorator(
        ContainerInterface $container,
        ?PDO $pdo = null
    ): ?TraceablePDODecorater {
        if ($pdo === null) {
            return null;
        }

        return new TraceablePDODecorater($pdo);
    }

    /**
     * Extend viserio profiler with data collector.
     *
     * @param \Psr\Container\ContainerInterface                  $container
     * @param null|\Viserio\Component\Contract\Profiler\Profiler $profiler
     *
     * @return null|\Viserio\Component\Contract\Profiler\Profiler
     */
    public static function extendProfiler(
        ContainerInterface $container,
        ?ProfilerContract $profiler = null
    ): ?ProfilerContract {
        if ($profiler !== null) {
            $profiler->addCollector(new PDODataCollector(
                $container->get(TraceablePDODecorater::class)
            ));
        }

        return $profiler;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Cookie\Middleware;

use ParagonIE\Halite\Alerts\InvalidMessage;
use ParagonIE\Halite\HiddenString;
use ParagonIE\Halite\Symmetric\Crypto;
use ParagonIE\Halite\Symmetric\EncryptionKey;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Viserio\Component\Contract\Cookie\Cookie as CookieContract;
use Viserio\Component\Cookie\Cookie;
use Viserio\Component\Cookie\RequestCookies;
use Viserio\Component\Cookie\ResponseCookies;
use Viserio\Component\Cookie\SetCookie;

class EncryptedCookiesMiddleware implements MiddlewareInterface
{
    /**
     * The encrypter key instance.
     *
     * @var \ParagonIE\Halite\Symmetric\EncryptionKey
     */
    protected $key;

    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array
     */
    protected $except = [];

    /**
     * Create a new encrypt Cookies instance.
     *
     * @param \ParagonIE\Halite\Symmetric\EncryptionKey $key
     */
    public function __construct(EncryptionKey $key)
    {
        $this->key = $key;
    }

    /**
     * Hide this from var_dump(), etc.
     *
     * @return array
     */
    public function __debugInfo(): array
    {
        return [
            'key' => 'private',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $request = $this->decrypt($request);

        $response = $handler->handle($request);

        return $this->encrypt($response);
    }

    /**
     * Decrypt the cookies on the request.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request
     *
     * @return \Psr\Http\Message\ServerRequestInterface
     */
    protected function decrypt(ServerRequestInterface $request): ServerRequestInterface
    {
        $cookies = RequestCookies::fromRequest($request);

        /** @var Cookie $cookie */
        foreach ($cookies->getAll() as $cookie) {
            $name = $cookie->getName();

            if ($this->isDisabled($name)) {
                continue;
            }

            try {
                $decryptedValue = Crypto::decrypt($cookie->getValue(), $this->key);
                $cookies        = $cookies->forget($name);
                $cookie         = $cookie->withValue($decryptedValue->getString());

                $cookies = $cookies->add($cookie);
            } catch (InvalidMessage $exception) {
                $cookies = $cookies->add(new Cookie($name, null));
            }
        }

        return $cookies->renderIntoCookieHeader($request);
    }

    /**
     * Encrypt the cookies on an outgoing response.
     *
     * @param \Psr\Http\Message\ResponseInterface $response
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function encrypt(ResponseInterface $response): ResponseInterface
    {
        $cookies = ResponseCookies::fromResponse($response);

        /** @var SetCookie $cookie */
        foreach ($cookies->getAll() as $cookie) {
            $name = $cookie->getName();

            if ($this->isDisabled($name)) {
                continue;
            }

            $cookies        = $cookies->forget($name);
            $encryptedValue = Crypto::encrypt(
                new HiddenString($cookie->getValue()),
                $this->key
            );

            $cookies = $cookies->add(
                $this->duplicate(
                    $cookie,
                    $encryptedValue
                )
            );
        }

        return $cookies->renderIntoSetCookieHeader($response);
    }

    /**
     * Duplicate a cookie with a new value.
     *
     * @param \Viserio\Component\Contract\Cookie\Cookie $cookie
     * @param string                                    $value
     *
     * @return \Viserio\Component\Contract\Cookie\Cookie
     */
    protected function duplicate(CookieContract $cookie, string $value): CookieContract
    {
        return new SetCookie(
            $cookie->getName(),
            $value,
            $cookie->getExpiresTime(),
            $cookie->getPath(),
            $cookie->getDomain(),
            $cookie->isSecure(),
            $cookie->isHttpOnly()
        );
    }

    /**
     * Determine whether encryption has been disabled for the given cookie.
     *
     * @param string $name
     *
     * @return bool
     */
    protected function isDisabled(string $name): bool
    {
        return \in_array($name, $this->except, true);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Foundation\Console;

use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use Viserio\Component\Console\Application as Cerebro;
use Viserio\Component\Contract\Console\Kernel as ConsoleKernelContract;
use Viserio\Component\Contract\Console\Terminable as TerminableContract;
use Viserio\Component\Contract\Exception\ConsoleHandler as ConsoleHandlerContract;
use Viserio\Component\Contract\Foundation\BootstrapState as BootstrapStateContract;
use Viserio\Component\Exception\Console\SymfonyConsoleOutput;
use Viserio\Component\Foundation\AbstractKernel;
use Viserio\Component\Foundation\BootstrapManager;

/**
 * @TODO add all public application methods
 *
 * @mixin \Viserio\Component\Console\Application
 *
 * @method int call(string $command, array $parameters = [], ?\Symfony\Component\Console\Output\OutputInterface $outputBuffer = null)
 */
class Kernel extends AbstractKernel implements ConsoleKernelContract, TerminableContract
{
    /**
     * The cerebro application instance.
     *
     * @var \Viserio\Component\Console\Application
     */
    protected $console;

    /**
     * List of allowed bootstrap types.
     *
     * @internal
     *
     * @var array
     */
    protected static $allowedBootstrapTypes = ['global', 'console'];

    /**
     * Create a new console kernel instance.
     */
    public function __construct()
    {
        if (! \defined('CEREBRO_BINARY')) {
            \define('CEREBRO_BINARY', 'cerebro');
        }

        parent::__construct();
    }

    /**
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call(string $method, array $arguments)
    {
        $this->bootstrap();

        return $this->getConsole()->{$method}(...$arguments);
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        $options = [
            'url'          => 'http://localhost',
            'version'      => self::VERSION,
            'console_name' => 'Cerebro',
        ];

        return \array_merge(parent::getDefaultOptions(), $options);
    }

    /**
     * {@inheritdoc}
     */
    public function handle(InputInterface $input, OutputInterface $output = null): int
    {
        try {
            $this->bootstrap();

            return $this->getConsole()->run($input, $output);
        } catch (Throwable $exception) {
            $this->reportException($exception);
            $this->renderException($output, $exception);

            return 1;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function terminate(InputInterface $input, int $status): void
    {
        $container = $this->getContainer();

        if (! $container->get(BootstrapManager::class)->hasBeenBootstrapped()) {
            return;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getAll(): array
    {
        $this->bootstrap();

        return $this->getConsole()->all();
    }

    /**
     * Get the output for the last run command.
     *
     * @return string
     */
    public function getOutput(): string
    {
        $this->bootstrap();

        return $this->getConsole()->getLastOutput();
    }

    /**
     * Register the given command with the console application.
     *
     * @param \Symfony\Component\Console\Command\Command $command
     *
     * @return void
     */
    public function registerCommand(SymfonyCommand $command): void
    {
        $this->bootstrap();

        $this->getConsole()->add($command);
    }

    /**
     * {@inheritdoc}
     */
    public function bootstrap(): void
    {
        $bootstrapManager = $this->getContainer()->get(BootstrapManager::class);

        if (! $bootstrapManager->hasBeenBootstrapped()) {
            $bootstraps = [];

            foreach ($this->getPreparedBootstraps() as $classes) {
                /** @var \Viserio\Component\Contract\Foundation\BootstrapState $class */
                foreach ($classes as $class) {
                    if (\in_array(BootstrapStateContract::class, \class_implements($class), true)) {
                        $method = 'add' . $class::getType() . 'Bootstrapping';

                        $bootstrapManager->{$method}($class::getBootstrapper(), [$class, 'bootstrap']);
                    } else {
                        /** @var \Viserio\Component\Contract\Foundation\Bootstrap $class */
                        $bootstraps[] = $class;
                    }
                }
            }

            $bootstrapManager->bootstrapWith($bootstraps);
        }
    }

    /**
     * Get the cerebro application instance.
     *
     * @return \Viserio\Component\Console\Application
     */
    protected function getConsole(): Cerebro
    {
        if ($this->console === null) {
            $console = $this->getContainer()->get(Cerebro::class);

            $console->setVersion($this->resolvedOptions['version']);
            $console->setName($this->resolvedOptions['console_name']);

            return $this->console = $console;
        }

        return $this->console;
    }

    /**
     * Report the exception to the exception handler.
     *
     * @param \Throwable $exception
     *
     * @return void
     */
    protected function reportException(Throwable $exception): void
    {
        $container = $this->getContainer();

        if ($container->has(ConsoleHandlerContract::class)) {
            $container->get(ConsoleHandlerContract::class)->report($exception);
        }
    }

    /**
     * Report the exception to the exception handler.
     *
     * @param null|\Symfony\Component\Console\Output\OutputInterface $output
     * @param \Throwable                                             $exception
     *
     * @return void
     */
    protected function renderException($output, Throwable $exception): void
    {
        if ($output instanceof ConsoleOutput) {
            $output = $output->getErrorOutput();
        }

        $container = $this->getContainer();

        if ($container->has(ConsoleHandlerContract::class)) {
            $container->get(ConsoleHandlerContract::class)
                ->render(new SymfonyConsoleOutput($output), $exception);
        } else {
            throw $exception;
        }
    }

    /**
     * Register the basic bindings into the container.
     *
     * @return void
     */
    protected function registerBaseBindings(): void
    {
        parent::registerBaseBindings();

        $kernel    = $this;
        $container = $this->getContainer();

        $container->singleton(ConsoleKernelContract::class, static function () use ($kernel) {
            return $kernel;
        });

        $container->alias(ConsoleKernelContract::class, self::class);
        $container->alias(ConsoleKernelContract::class, 'console_kernel');
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Foundation;

use Closure;
use ReflectionObject;
use Viserio\Component\Container\Container;
use Viserio\Component\Contract\Container\Container as ContainerContract;
use Viserio\Component\Contract\Foundation\Environment as EnvironmentContract;
use Viserio\Component\Contract\Foundation\Kernel as KernelContract;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Contract\OptionsResolver\RequiresMandatoryOptions as RequiresMandatoryOptionsContract;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

abstract class AbstractKernel implements
    KernelContract,
    ProvidesDefaultOptionsContract,
    RequiresComponentConfigContract,
    RequiresMandatoryOptionsContract
{
    use OptionsResolverTrait;

    /**
     * The current framework full version.
     *
     * @var string
     */
    public const VERSION = '1.0.0-DEV';

    /**
     * The current framework version id.
     *
     * @var int
     */
    public const VERSION_ID = 10000;

    /**
     * The current framework "major" version.
     *
     * @var int
     */
    public const MAJOR_VERSION = 1;

    /**
     * The current framework "minor" version.
     *
     * @var int
     */
    public const MINOR_VERSION = 0;

    /**
     * The current framework "release" version.
     *
     * @var int
     */
    public const RELEASE_VERSION = 0;

    /**
     * The current framework "extra" version.
     *
     * @var string
     */
    public const EXTRA_VERSION = 'DEV';

    /**
     * @var string
     */
    public const END_OF_MAINTENANCE = '?';

    /**
     * @var string
     */
    public const END_OF_LIFE = '?';

    /**
     * List of allowed bootstrap types.
     *
     * @internal
     *
     * @var array
     */
    protected static $allowedBootstrapTypes = ['global'];

    /**
     * Container instance.
     *
     * @var \Viserio\Component\Contract\Container\Container
     */
    protected $container;

    /**
     * Project root path.
     *
     * @var string
     */
    protected $rootDir;

    /**
     * Project root dirs.
     *
     * @var string
     */
    protected $projectDirs;

    /**
     * The environment file to load during bootstrapping.
     *
     * @var string
     */
    protected $environmentFile = '.env';

    /**
     * The custom environment path defined by the developer.
     *
     * @var string
     */
    protected $environmentPath;

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

    /**
     * Create a new kernel instance.
     *
     * Let's start! Making magic!
     */
    public function __construct()
    {
        $this->rootDir     = $this->getRootDir();
        $this->projectDirs = $this->initProjectDirs();

        $this->container = $this->initializeContainer();
        $this->registerBaseBindings();
    }

    /**
     * {@inheritdoc}
     */
    public function getContainer(): ContainerContract
    {
        return $this->container;
    }

    /**
     * {@inheritdoc}
     */
    public function getRootDir(): string
    {
        if ($this->rootDir === null) {
            $reflection = new ReflectionObject($this);
            $dir        = $rootDir = \dirname($reflection->getFileName());

            while (! \file_exists($dir . \DIRECTORY_SEPARATOR . 'composer.json')) {
                if (\dirname($dir) === $dir) {
                    return $this->rootDir = $rootDir;
                }

                $dir = \dirname($dir);
            }

            $this->rootDir = $dir;
        }

        return $this->rootDir;
    }

    /**
     * {@inheritdoc}
     */
    public function getEnvironmentFile(): string
    {
        return $this->environmentFile ?: '.env';
    }

    /**
     * {@inheritdoc}
     */
    public function getEnvironmentPath(): string
    {
        return $this->environmentPath ?: $this->rootDir;
    }

    /**
     * {@inheritdoc}
     */
    public static function getDimensions(): array
    {
        return ['viserio', 'app'];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        return [
            'timezone' => 'UTC',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function getMandatoryOptions(): array
    {
        return [
            'env',
            'debug',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function setKernelConfigurations($config): void
    {
        $this->resolvedOptions = self::resolveOptions($config);

        \date_default_timezone_set($this->resolvedOptions['viserio']['app']['timezone'] ?? 'UTC');
        \mb_internal_encoding('UTF-8');
    }

    /**
     * {@inheritdoc}
     */
    public function getKernelConfigurations(): array
    {
        return $this->resolvedOptions;
    }

    /**
     * {@inheritdoc}
     */
    public function isLocal(): bool
    {
        return $this->resolvedOptions['env'] === 'local';
    }

    /**
     * {@inheritdoc}
     */
    public function isRunningUnitTests(): bool
    {
        return $this->resolvedOptions['env'] === 'testing';
    }

    /**
     * {@inheritdoc}
     */
    public function isRunningInConsole(): bool
    {
        return \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true);
    }

    /**
     * {@inheritdoc}
     */
    public function isDownForMaintenance(): bool
    {
        return \file_exists($this->getStoragePath('framework' . \DIRECTORY_SEPARATOR . 'down'));
    }

    /**
     * {@inheritdoc}
     */
    public function getAppPath(string $path = ''): string
    {
        return $this->projectDirs['app-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getConfigPath(string $path = ''): string
    {
        return $this->projectDirs['config-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getDatabasePath(string $path = ''): string
    {
        return $this->projectDirs['database-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getPublicPath(string $path = ''): string
    {
        return $this->projectDirs['public-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getStoragePath(string $path = ''): string
    {
        return $this->projectDirs['storage-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getResourcePath(string $path = ''): string
    {
        return $this->projectDirs['resources-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getLangPath(): string
    {
        return $this->getResourcePath('lang');
    }

    /**
     * {@inheritdoc}
     */
    public function getRoutesPath(string $path = ''): string
    {
        return $this->projectDirs['routes-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getTestsPath(string $path = ''): string
    {
        return $this->projectDirs['tests-dir'] . ($path ? \DIRECTORY_SEPARATOR . $path : $path);
    }

    /**
     * {@inheritdoc}
     */
    public function useEnvironmentPath(string $path): KernelContract
    {
        $this->environmentPath = $path;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function loadEnvironmentFrom(string $file): KernelContract
    {
        $this->environmentFile = $file;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getEnvironmentFilePath(): string
    {
        return $this->getEnvironmentPath() . \DIRECTORY_SEPARATOR . $this->getEnvironmentFile();
    }

    /**
     * {@inheritdoc}
     */
    public function detectEnvironment(Closure $callback): string
    {
        $args = $_SERVER['argv'] ?? null;
        $env  = $this->getContainer()->get(EnvironmentContract::class)->detect($callback, $args);

        $this->resolvedOptions['env'] = $env;

        return $env;
    }

    /**
     * {@inheritdoc}
     */
    public function getEnvironment(): string
    {
        return $this->resolvedOptions['env'];
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug(): bool
    {
        return $this->resolvedOptions['debug'];
    }

    /**
     * Register all of the application / kernel service providers.
     *
     * @return array
     */
    public function registerServiceProviders(): array
    {
        $providersPath = $this->getConfigPath('serviceproviders.php');

        $providers = [];

        if (\file_exists($providersPath)) {
            $providers = (array) require $providersPath;
        }

        $providersEnvPath = $this->getConfigPath($this->getEnvironment() . \DIRECTORY_SEPARATOR . 'serviceproviders.php');

        if (\file_exists($providersEnvPath)) {
            $providers = \array_merge($providers, (array) require $providersEnvPath);
        }

        return $providers;
    }

    /**
     * Merge composer project dir settings with the default narrowspark dir settings.
     *
     * @return array
     */
    protected function initProjectDirs(): array
    {
        if ($this->projectDirs === null) {
            $jsonFile = $this->rootDir . \DIRECTORY_SEPARATOR . 'composer.json';
            $dirs     = [
                'app-dir'       => $this->rootDir . \DIRECTORY_SEPARATOR . 'app',
                'config-dir'    => $this->rootDir . \DIRECTORY_SEPARATOR . 'config',
                'database-dir'  => $this->rootDir . \DIRECTORY_SEPARATOR . 'database',
                'public-dir'    => $this->rootDir . \DIRECTORY_SEPARATOR . 'public',
                'resources-dir' => $this->rootDir . \DIRECTORY_SEPARATOR . 'resources',
                'routes-dir'    => $this->rootDir . \DIRECTORY_SEPARATOR . 'routes',
                'tests-dir'     => $this->rootDir . \DIRECTORY_SEPARATOR . 'tests',
                'storage-dir'   => $this->rootDir . \DIRECTORY_SEPARATOR . 'storage',
            ];

            if (\file_exists($jsonFile)) {
                $jsonData = \json_decode(\file_get_contents($jsonFile), true);
                $extra    = $jsonData['extra'] ?? [];

                foreach ($extra as $key => $value) {
                    if (\array_key_exists($key, $dirs)) {
                        $dirs[$key] = $this->rootDir . \DIRECTORY_SEPARATOR . \ltrim($value, '/\\');
                    }
                }
            }

            $this->projectDirs = $dirs;
        }

        return $this->projectDirs;
    }

    /**
     * Register the basic bindings into the container.
     *
     * @return void
     */
    protected function registerBaseBindings(): void
    {
        $kernel    = $this;
        $container = $this->getContainer();

        $container->singleton(EnvironmentContract::class, EnvironmentDetector::class);
        $container->singleton(KernelContract::class, static function () use ($kernel) {
            return $kernel;
        });
        $container->singleton(BootstrapManager::class, BootstrapManager::class);

        $container->alias(KernelContract::class, self::class);
        $container->alias(KernelContract::class, 'kernel');
    }

    /**
     * Initializes the service container.
     *
     * @return \Viserio\Component\Contract\Container\Container
     */
    protected function initializeContainer(): ContainerContract
    {
        return new Container();
    }

    /**
     * Returns prepared bootstrap classes, sorted and filtered after static::$allowedBootstrapTypes.
     *
     * @return array
     */
    protected function getPreparedBootstraps(): array
    {
        $preparedBootstraps = [];

        /** @var \Viserio\Component\Contract\Foundation\Bootstrap $class */
        foreach ((array) require $this->getConfigPath('bootstrap.php') as $class => $data) {
            foreach ((array) $data as $type) {
                if (\in_array($type, static::$allowedBootstrapTypes, true)) {
                    $preparedBootstraps[$class::getPriority()][] = $class;
                }
            }
        }

        \ksort($preparedBootstraps);

        return $preparedBootstraps;
    }
}