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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
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\StaticalProxy\AliasLoader as AliasLoaderContract;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;
use Viserio\Component\StaticalProxy\AliasLoader;

class AliasLoaderServiceProvider implements
    ServiceProviderInterface,
    RequiresComponentConfigContract,
    ProvidesDefaultOptionsContract
{
    use OptionsResolverTrait;

    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [
            AliasLoaderContract::class => [self::class, 'createAliasLoader'],
            AliasLoader::class         => static function (ContainerInterface $container) {
                return $container->get(AliasLoaderContract::class);
            },
            'alias' => static function (ContainerInterface $container) {
                return $container->get(AliasLoaderContract::class);
            },
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [];
    }

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

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        return [
            'aliases'         => [],
            'cache_path'      => null,
            'real_time_proxy' => false,
        ];
    }

    /**
     * Create a new Alias loader.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Contract\StaticalProxy\AliasLoader
     */
    public static function createAliasLoader(ContainerInterface $container): AliasLoaderContract
    {
        $options = self::resolveOptions($container->get('config'));

        $loader    = new AliasLoader($options['aliases']);
        $cachePath = self::getCachePath($container, $options);

        if ($cachePath !== null) {
            $loader->setCachePath($cachePath);

            if ($options['real_time_proxy'] === true) {
                $loader->enableRealTimeStaticalProxy();
            }
        }

        return $loader;
    }

    /**
     * Get real-time proxy cache path.
     *
     * @param \Psr\Container\ContainerInterface $container
     * @param array                             $options
     *
     * @return null|string
     */
    private static function getCachePath(ContainerInterface $container, array $options): ?string
    {
        $cachePath = $options['cache_path'];

        if ($cachePath === null && $container->has(KernelContract::class)) {
            $cachePath = $container->get(KernelContract::class)->getStoragePath('staticalproxy');
        }

        return $cachePath;
    }
}


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

use Closure;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use RuntimeException;
use Viserio\Component\Contract\Bus\QueueingDispatcher as QueueingDispatcherContract;
use Viserio\Component\Contract\Queue\QueueConnector as QueueContract;
use Viserio\Component\Contract\Queue\ShouldQueue as ShouldQueueContract;

class QueueingDispatcher extends Dispatcher implements QueueingDispatcherContract
{
    /**
     * The queue resolver callback.
     *
     * @var null|\Closure
     */
    protected $queueResolver;

    /**
     * Create a new queue command dispatcher instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     * @param null|\Closure                     $queueResolver
     */
    public function __construct(ContainerInterface $container, Closure $queueResolver = null)
    {
        $this->queueResolver = $queueResolver;

        parent::__construct($container);
    }

    /**
     * {@inheritdoc}
     */
    public function dispatch($command, Closure $afterResolving = null)
    {
        if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
            return $this->dispatchToQueue($command);
        }

        return parent::dispatch($command, $afterResolving);
    }

    /**
     * {@inheritdoc}
     */
    public function dispatchToQueue($command)
    {
        $connection = $command->connection ?? null;
        $queue      = \call_user_func($this->queueResolver, $connection);

        if (! $queue instanceof QueueContract) {
            throw new RuntimeException('Queue resolver did not return a Queue implementation.');
        }

        if (\method_exists($command, 'queue')) {
            return $command->queue($queue, $command);
        }

        return $this->pushCommandToQueue($queue, $command);
    }

    /**
     * Push the command onto the given queue instance.
     *
     * @param \Viserio\Component\Contract\Queue\QueueConnector $queue
     * @param mixed                                            $command
     *
     * @return mixed
     */
    protected function pushCommandToQueue(QueueContract $queue, $command)
    {
        if (isset($command->queue, $command->delay)) {
            return $queue->laterOn($command->queue, $command->delay, $command);
        }

        if (isset($command->queue)) {
            return $queue->pushOn($command->queue, $command);
        }

        if (isset($command->delay)) {
            return $queue->later($command->delay, $command);
        }

        return $queue->push($command);
    }

    /**
     * Determine if the given command should be queued.
     *
     * @param mixed $command
     *
     * @return bool
     */
    protected function commandShouldBeQueued($command): bool
    {
        if ($command instanceof ShouldQueueContract) {
            return true;
        }

        $reflection = new ReflectionClass($this->getHandlerClass($command));

        return $reflection->implementsInterface(ShouldQueueContract::class);
    }
}


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

use Symfony\Component\Debug\DebugClassLoader;
use Viserio\Component\Exception\Console\Handler as ConsoleHandler;
use Viserio\Component\Exception\ErrorHandler;
use Viserio\Component\Exception\ExceptionIdentifier;
use Viserio\Component\Exception\ExceptionInfo;
use Viserio\Component\Exception\Http\Handler as HttpHandler;

trait RegisterAndUnregisterTrait
{
    /**
     * {@inheritdoc}
     */
    public function register(): void
    {
        \error_reporting(\E_ALL);

        // Ensures we don't hit https://bugs.php.net/42098
        \class_exists(HttpHandler::class);
        \class_exists(ConsoleHandler::class);
        \class_exists(ErrorHandler::class);
        \class_exists(ExceptionIdentifier::class);
        \class_exists(ExceptionInfo::class);

        // The DebugClassLoader attempts to throw more helpful exceptions
        // when a class isn't found by the registered autoloaders.
        DebugClassLoader::enable();

        $this->registerErrorHandler();

        $this->registerExceptionHandler();

        if ($this->resolvedOptions['env'] !== 'testing') {
            $this->registerShutdownHandler();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function unregister(): void
    {
        \restore_error_handler();
    }

    /**
     * Register the PHP error handler.
     *
     * @return void
     */
    abstract protected function registerErrorHandler(): void;

    /**
     * Register the PHP exception handler.
     *
     * @return void
     */
    abstract protected function registerExceptionHandler(): void;

    /**
     * Register the PHP shutdown handler.
     *
     * @return void
     */
    abstract protected function registerShutdownHandler(): void;
}


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

use Cache\SessionHandler\Psr6SessionHandler;
use ParagonIE\Halite\KeyFactory;
use SessionHandlerInterface;
use Viserio\Component\Contract\Cache\Manager as CacheManagerContract;
use Viserio\Component\Contract\Cache\Traits\CacheManagerAwareTrait;
use Viserio\Component\Contract\Cookie\QueueingFactory as JarContract;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\Session\Exception\RuntimeException;
use Viserio\Component\Contract\Session\Store as StoreContract;
use Viserio\Component\Manager\AbstractManager;
use Viserio\Component\Session\Handler\CookieSessionHandler;
use Viserio\Component\Session\Handler\FileSessionHandler;
use Viserio\Component\Session\Handler\MigratingSessionHandler;
use Viserio\Component\Session\Handler\NullSessionHandler;

class SessionManager extends AbstractManager implements ProvidesDefaultOptionsContract
{
    use CacheManagerAwareTrait;

    /**
     * Encryption key instance.
     *
     * @var \ParagonIE\Halite\Symmetric\EncryptionKey
     */
    private $key;

    /**
     * CookieJar instance.
     *
     * @var \Viserio\Component\Contract\Cookie\QueueingFactory
     */
    private $cookieJar;

    /**
     * Create a new session manager instance.
     *
     * @param array|\ArrayAccess $config
     *
     * @throws \ParagonIE\Halite\Alerts\CannotPerformOperation
     * @throws \ParagonIE\Halite\Alerts\InvalidKey
     * @throws \TypeError
     */
    public function __construct($config)
    {
        parent::__construct($config);

        $this->key = KeyFactory::loadEncryptionKey($this->resolvedOptions['key_path']);
    }

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

    /**
     * Set the cookie jar instance.
     *
     * @param \Viserio\Component\Contract\Cookie\QueueingFactory $cookieJar
     */
    public function setCookieJar(JarContract $cookieJar): void
    {
        $this->cookieJar = $cookieJar;
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        return [
            'default'  => 'array',
            'lifetime' => 7200, // 2 hours
            'encrypt'  => true,
            'drivers'  => [],
            'cookie'   => [
                'name'            => 'NSSESSID',
                'path'            => '/',
                'domain'          => null,
                'secure'          => null,
                'http_only'       => true,
                'samesite'        => false,
                'expire_on_close' => false,
                'lottery'         => [2, 100],
            ],
        ];
    }

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

    /**
     * Create an instance of the file session driver.
     *
     * @param array $config
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function createFileDriver(array $config): StoreContract
    {
        return $this->buildSession(
            new FileSessionHandler(
                $config['path'],
                $this->resolvedOptions['lifetime']
            )
        );
    }

    /**
     * Create an instance of the "cookie" session driver.
     *
     * @throws \Viserio\Component\Contract\Session\Exception\RuntimeException
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function createCookieDriver(): StoreContract
    {
        if ($this->cookieJar === null) {
            throw new RuntimeException(\sprintf('No instance of [%s] found.', JarContract::class));
        }

        return $this->buildSession(
            new CookieSessionHandler(
                $this->cookieJar,
                $this->resolvedOptions['lifetime']
            )
        );
    }

    /**
     * Create an instance of the Array session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function createArrayDriver(): StoreContract
    {
        return $this->buildSession(new NullSessionHandler());
    }

    /**
     * Create an instance of the Memcached session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     *
     * @codeCoverageIgnore
     */
    protected function createMemcachedDriver(): StoreContract
    {
        return $this->createCacheBased('memcached');
    }

    /**
     * Create an instance of the Memcache session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     *
     * @codeCoverageIgnore
     */
    protected function createMemcacheDriver(): StoreContract
    {
        return $this->createCacheBased('memcache');
    }

    /**
     * Create an instance of the Mongodb session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     *
     * @codeCoverageIgnore
     */
    protected function createMongodbDriver(): StoreContract
    {
        return $this->createCacheBased('mongodb');
    }

    /**
     * Create an instance of the Predis session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     *
     * @codeCoverageIgnore
     */
    protected function createPredisDriver(): StoreContract
    {
        return $this->createCacheBased('predis');
    }

    /**
     * Create an instance of the Redis session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     *
     * @codeCoverageIgnore
     */
    protected function createRedisDriver(): StoreContract
    {
        return $this->createCacheBased('redis');
    }

    /**
     * Create an instance of the Filesystem session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function createFilesystemDriver(): StoreContract
    {
        return $this->createCacheBased('filesystem');
    }

    /**
     * Create an instance of the APCu session driver.
     *
     * @return \Viserio\Component\Contract\Session\Store
     *
     * @codeCoverageIgnore
     */
    protected function createApcuDriver(): StoreContract
    {
        return $this->createCacheBased('apcu');
    }

    /**
     * Create an instance of the Migrating session driver.
     *
     * @param array $config
     *
     * @throws \Viserio\Component\Contract\Session\Exception\RuntimeException
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function createMigratingDriver(array $config): StoreContract
    {
        if (! isset($config['current'], $config['write_only'])) {
            throw new RuntimeException('The MigratingSessionHandler needs a current and write only handler.');
        }

        $currentHandler   = $this->getDriver($config['current']);
        $writeOnlyHandler = $this->getDriver($config['write_only']);

        return $this->buildSession(
            new MigratingSessionHandler($currentHandler->getHandler(), $writeOnlyHandler->getHandler())
        );
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    protected function callCustomCreator(string $driver, array $options = []): StoreContract
    {
        return $this->buildSession(parent::callCustomCreator($driver, $options));
    }

    /**
     * Create the cache based session handler instance.
     *
     * @param string $driver
     *
     * @throws \Viserio\Component\Contract\Session\Exception\RuntimeException
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function createCacheBased($driver): StoreContract
    {
        if ($this->cacheManager === null) {
            throw new RuntimeException(\sprintf('No instance of [%s] found.', CacheManagerContract::class));
        }

        return $this->buildSession(
            new Psr6SessionHandler(
                clone $this->cacheManager->getDriver($driver),
                ['ttl' => $this->resolvedOptions['lifetime'], 'prefix' => 'ns_ses_']
            )
        );
    }

    /**
     * Build the session instance.
     *
     * @param \SessionHandlerInterface $handler
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function buildSession(SessionHandlerInterface $handler): StoreContract
    {
        if ($this->resolvedOptions['encrypt'] === true) {
            return $this->buildEncryptedSession($handler);
        }

        return new Store($this->resolvedOptions['cookie']['name'], $handler);
    }

    /**
     * Build the encrypted session instance.
     *
     * @param \SessionHandlerInterface $handler
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function buildEncryptedSession(SessionHandlerInterface $handler): StoreContract
    {
        return new EncryptedStore(
            $this->resolvedOptions['cookie']['name'],
            $handler,
            $this->key
        );
    }

    /**
     * {@inheritdoc}
     */
    protected static function getConfigName(): string
    {
        return 'session';
    }
}


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

use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Throwable;
use Viserio\Component\Contract\Exception\Transformer as TransformerContract;

class UndefinedFunctionFatalErrorTransformer implements TransformerContract
{
    /**
     * {@inheritdoc}
     */
    public function transform(Throwable $exception): Throwable
    {
        if ($exception instanceof FatalErrorException && ! $exception instanceof OutOfMemoryException) {
            $error = [
                'type'    => $exception->getSeverity(),
                'message' => $exception->getMessage(),
                'file'    => $exception->getFile(),
                'line'    => $exception->getLine(),
            ];

            $handler = new UndefinedFunctionFatalErrorHandler();

            if ($e = $handler->handleError($error, $exception)) {
                return $e;
            }
        }

        return $exception;
    }
}


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

use Viserio\Component\Contract\Routing\Exception\LogicException;
use Viserio\Component\Contract\Routing\Exception\UnexpectedValueException;

class Action
{
    /**
     * Parse the given action into an array.
     *
     * @param string $uri
     * @param mixed  $action
     *
     * @throws \Viserio\Component\Contract\Routing\Exception\UnexpectedValueException if invalid route action
     * @throws \Viserio\Component\Contract\Routing\Exception\LogicException           if no action found
     *
     * @return array
     */
    public static function parse(string $uri, $action): array
    {
        // If no action is passed in right away, we assume the user will make use of
        // fluent routing. In that case, we set a default closure, to be executed
        // if the user never explicitly sets an action to handle the given uri.
        if ($action === null) {
            return static::missingAction($uri);
        }

        // If the action is already a Closure instance, we will just set that instance
        // as the "uses" property.
        if (\is_callable($action)) {
            return ['uses' => $action];
        }

        // If no "uses" property has been set, we will dig through the array to find a
        // Closure instance within this list. We will set the first Closure we come across.
        if (! isset($action['uses'])) {
            $callback = static function ($key, $value) {
                return \is_callable($value) && \is_numeric($key);
            };

            $action['uses'] = self::getFirst($action, $callback);
        }

        if (\is_string($action['uses']) && \strpos($action['uses'], '@') === false) {
            if (! \method_exists($action['uses'], '__invoke')) {
                throw new UnexpectedValueException(\sprintf(
                    'Invalid route action: [%s].',
                    $action['uses']
                ));
            }

            $action['uses'] = $action['uses'] . '@__invoke';
        }

        return $action;
    }

    /**
     * Get an action for a route that has no action.
     *
     * @param string $uri
     *
     * @throws \Viserio\Component\Contract\Routing\Exception\LogicException if no action found
     *
     * @return array
     */
    protected static function missingAction(string $uri): array
    {
        return ['uses' => static function () use ($uri): void {
            throw new LogicException(\sprintf('Route for [%s] has no action.', $uri));
        }];
    }

    /**
     * Return the first element in an array passing a given truth test.
     *
     * @param array    $array
     * @param callable $callback
     *
     * @return mixed
     */
    protected static function getFirst(array $array, callable $callback)
    {
        foreach ($array as $key => $value) {
            if ($callback($key, $value)) {
                return $value;
            }
        }

        return null;
    }
}


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

use DOMDocument;
use PHPUnit\Util\Xml;
use SimpleXMLElement;
use Viserio\Component\Contract\Parser\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Parser\Exception\ParseException;
use Viserio\Component\Contract\Parser\Parser as ParserContract;
use Viserio\Component\Parser\Utils\XliffUtils;
use Viserio\Component\Parser\Utils\XmlUtils;

/**
 * Some of this code has been ported from Symfony. The original
 * code is (c) Fabien Potencier <fabien@symfony.com>.
 *
 * Good article about xliff @link http://www.wikiwand.com/en/XLIFF
 */
class XliffParser implements ParserContract
{
    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        try {
            $dom = XmlUtils::loadString($payload);

            $xliffVersion = XliffUtils::getVersionNumber($dom);

            if ($errors = XliffUtils::validateSchema($dom)) {
                throw new InvalidArgumentException(\sprintf(
                    'Invalid resource provided: [%s]; Errors: %s.',
                    $xliffVersion,
                    XmlUtils::getErrorsAsString($errors)
                ));
            }

            if ($xliffVersion === '2.0') {
                return $this->extractXliffVersion2($dom);
            }

            return $this->extractXliffVersion1($dom);
        } catch (InvalidArgumentException $exception) {
            throw new ParseException([
                'message' => $exception->getMessage(),
                'code'    => $exception->getCode(),
                'file'    => $exception->getFile(),
                'line'    => $exception->getLine(),
            ]);
        }
    }

    /**
     * Extract messages and metadata from DOMDocument into a MessageCatalogue.
     *
     * @param \DOMDocument $dom
     *
     * @throws \Viserio\Component\Contract\Parser\Exception\ParseException
     *
     * @return array
     */
    private function extractXliffVersion1(DOMDocument $dom): array
    {
        $xml = XmlUtils::importDom($dom);

        $encoding = \mb_strtoupper($dom->encoding);
        $datas    = [
            'version'         => '1.2',
            'source-language' => '',
            'target-language' => '',
        ];

        foreach ($xml->file->attributes() as $key => $value) {
            if ($key === 'source-language' || $key === 'target-language') {
                $datas[$key] = (string) $value;
            }
        }

        $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2');

        foreach ((array) $xml->xpath('//xliff:trans-unit') as $trans) {
            $attributes = $trans->attributes();
            $id         = (string) ($attributes['resname'] ?? $trans->source ?? '');

            if ($id === '') {
                continue;
            }

            $datas[$id] = [
                'source' => (string) $trans->source,
                // If the xlf file has another encoding specified, try to convert it because
                // simple_xml will always return utf-8 encoded values
                'target' => isset($trans->target) ? self::utf8ToCharset((string) $trans->target, $encoding) : null,
            ];

            if (isset($attributes['id'])) {
                $datas[$id]['id'] = (string) $attributes['id'];
            }

            // If the translation has a note
            if (isset($trans->note)) {
                $datas[$id]['notes'] = self::parseNotes($trans->note, $encoding);
            }

            if (isset($trans->target) && ($attributes = $trans->target->attributes())) {
                $datas[$id]['target-attributes'] = [];

                foreach ($attributes as $key => $value) {
                    $datas[$id]['target-attributes'][$key] = (string) $value;
                }
            }
        }

        return $datas;
    }

    /**
     * Parse xliff notes.
     *
     * @param \SimpleXMLElement $noteElement
     * @param null|string       $encoding
     *
     * @return array
     */
    private static function parseNotes(SimpleXMLElement $noteElement, ?string $encoding = null): array
    {
        $notes = [];

        /** @var \SimpleXMLElement $xmlNote */
        foreach ($noteElement as $xmlNote) {
            $noteAttributes = $xmlNote->attributes();
            $note           = ['content' => self::utf8ToCharset((string) $xmlNote, $encoding)];

            if (isset($noteAttributes['priority'])) {
                $note['priority'] = (int) $noteAttributes['priority'];
            }

            if (isset($noteAttributes['from'])) {
                $note['from'] = (string) $noteAttributes['from'];
            }

            $notes[] = $note;
        }

        return $notes;
    }

    /**
     * Extract messages and metadata from DOMDocument into a MessageCatalogue.
     *
     * @param \DOMDocument $dom
     *
     * @throws \Viserio\Component\Contract\Parser\Exception\ParseException
     *
     * @return array
     */
    private function extractXliffVersion2(DOMDocument $dom): array
    {
        $xml = XmlUtils::importDom($dom);

        $encoding = \mb_strtoupper($dom->encoding);
        $datas    = [
            'version' => '2.0',
            'srcLang' => '',
            'trgLang' => '',
        ];

        foreach ($xml->attributes() as $key => $value) {
            if ($key === 'srcLang' || $key === 'trgLang') {
                $datas[$key] = (string) $value;
            }
        }

        $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0');

        foreach ((array) $xml->xpath('//xliff:unit') as $unit) {
            $unitAttr = (array) $unit->attributes();
            $unitAttr = \reset($unitAttr);
            $source   = (string) $unit->segment->source;
            $target   = null;
            $id       = $unitAttr['id'];

            if (isset($unit->segment->target)) {
                $target = self::utf8ToCharset((string) $unit->segment->target, $encoding);
            }

            $datas[$id] = [
                'source' => $source,
                // If the xlf file has another encoding specified, try to convert it because
                // simple_xml will always return utf-8 encoded values
                'target' => $target,
            ];

            if ($target !== null && $unit->segment->target->attributes()) {
                $datas[$id]['target-attributes'] = [];

                foreach ($unit->segment->target->attributes() as $key => $value) {
                    $datas[$id]['target-attributes'][$key] = (string) $value;
                }
            }

            if (isset($unit->notes)) {
                $metadata['notes'] = [];

                foreach ($unit->notes->note as $noteNode) {
                    $note = [];

                    foreach ($noteNode->attributes() as $key => $value) {
                        $note[$key] = (string) $value;
                    }

                    $note['content']       = (string) $noteNode;
                    $datas[$id]['notes'][] = $note;
                }
            }
        }

        return $datas;
    }

    /**
     * Convert a UTF8 string to the specified encoding.
     *
     * @param string      $content  String to decode
     * @param null|string $encoding Target encoding
     *
     * @return string
     */
    private static function utf8ToCharset(string $content, string $encoding = null): string
    {
        if ($encoding !== 'UTF-8' && $encoding !== null) {
            return \mb_convert_encoding($content, $encoding, 'UTF-8');
        }

        return $content;
    }
}


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

use DOMDocument;
use Viserio\Component\Contract\Parser\Dumper as DumperContract;
use Viserio\Component\Contract\Parser\Exception\DumpException;

/**
 * Some of this code has been ported from Symfony. The original
 * code is (c) Fabien Potencier <fabien@symfony.com>.
 *
 * Good article about xliff @link http://www.wikiwand.com/en/XLIFF
 */
class XliffDumper implements DumperContract
{
    /**
     * {@inheritdoc}
     *
     * For xliff v1:
     *    array[]
     *        ['version']                    string Need to be 1.2
     *        ['source-language']            string
     *        ['target-language']            string
     *        ['encoding']                   string A optional option; to set the file encoding
     *        array['resname']               string
     *            ['source']                 string
     *            ['target']                 string
     *            ['id']                     string A optional option; if this is missing, md5 hash from resname is generated
     *            array['target-attributes']        A optional array to set the target attributes; simple key value array
     *            array['notes']                    A optional array to generate notes
     *                ['content']            string
     *                ['from']               string optional
     *                ['priority']           string optional
     * For xliff v2:
     *    array[]
     *        ['version']                    string Need to be 2.0
     *        ['srcLang']                    string
     *        ['trgLang']                    string
     *        ['encoding']                   string A optional option; to set the file encoding
     *        array['notes']                        A optional array to generate notes
     *            ['content']                string
     *            ['from']                   string optional
     *            ['priority']               string optional
     *        array['id']                    string
     *            ['source']                 string
     *            ['target']                 string
     *            array['target-attributes'] array  A optional array to set the target attributes; simple key value array
     */
    public function dump(array $data): string
    {
        $version = $data['version'];

        unset($data['version']);

        if ($version === '1.2') {
            return self::dumpXliffVersion1($data);
        }

        if ($version === '2.0') {
            return self::dumpXliffVersion2($data);
        }

        throw new DumpException(\sprintf('No support implemented for dumping XLIFF version [%s].', $version));
    }

    /**
     * Dump xliff version 1.
     *
     * @param array $data
     *
     * @return string
     */
    private static function dumpXliffVersion1(array $data): string
    {
        $sourceLanguage = $data['source-language'];
        $targetLanguage = $data['target-language'];
        $encoding       = 'UTF-8';

        if (isset($data['encoding'])) {
            $encoding = $data['encoding'];

            unset($data['encoding']);
        }

        unset($data['source-language'], $data['target-language']);

        $dom               = new DOMDocument('1.0', $encoding);
        $dom->formatOutput = true;

        $xliff = $dom->appendChild($dom->createElement('xliff'));
        $xliff->setAttribute('version', '1.2');
        $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2');

        $xliffFile = $xliff->appendChild($dom->createElement('file'));
        $xliffFile->setAttribute('source-language', \str_replace('_', '-', $sourceLanguage));

        if ($targetLanguage !== '') {
            $xliffFile->setAttribute('target-language', \str_replace('_', '-', $targetLanguage));
        }

        $xliffFile->setAttribute('datatype', 'plaintext');
        $xliffFile->setAttribute('original', 'file.ext');

        $xliffBody = $xliffFile->appendChild($dom->createElement('body'));

        foreach ($data as $resname => $translation) {
            $unit = $dom->createElement('trans-unit');
            $unit->setAttribute('id', $translation['id'] ?? \md5($resname));
            $unit->setAttribute('resname', $resname);

            $source = $unit->appendChild($dom->createElement('source'));
            $source->appendChild($dom->createTextNode($translation['source']));

            $targetElement = $dom->createElement('target');

            if (isset($translation['target-attributes'])) {
                foreach ($translation['target-attributes'] as $key => $value) {
                    $targetElement->setAttribute($key, $value);
                }
            }

            $target = $unit->appendChild($targetElement);

            // Does the target contain characters requiring a CDATA section?
            if (\preg_match('/[&<>]/', $translation['target']) === 1) {
                $target->appendChild($dom->createCDATASection($translation['target']));
            } else {
                $target->appendChild($dom->createTextNode($translation['target']));
            }

            if (isset($translation['notes'])) {
                foreach ($translation['notes'] as $note) {
                    $noteElement = $dom->createElement('note');
                    $noteElement->appendChild($dom->createTextNode($note['content'] ?? ''));

                    unset($note['content']);

                    foreach ((array) $note as $name => $value) {
                        $noteElement->setAttribute($name, $value);
                    }

                    $unit->appendChild($noteElement);
                }
            }

            $xliffBody->appendChild($unit);
        }

        return $dom->saveXML();
    }

    /**
     * Dump xliff version 2.
     *
     * @param array $data
     *
     * @return string
     */
    private static function dumpXliffVersion2(array $data): string
    {
        $sourceLanguage = $data['srcLang'];
        $targetLanguage = $data['trgLang'];
        $encoding       = 'UTF-8';

        if (isset($data['encoding'])) {
            $encoding = $data['encoding'];

            unset($data['encoding']);
        }

        unset($data['srcLang'], $data['trgLang']);

        $dom               = new DOMDocument('1.0', $encoding);
        $dom->formatOutput = true;

        $xliff = $dom->appendChild($dom->createElement('xliff'));
        $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0');
        $xliff->setAttribute('version', '2.0');
        $xliff->setAttribute('srcLang', \str_replace('_', '-', $sourceLanguage));
        $xliff->setAttribute('trgLang', \str_replace('_', '-', $targetLanguage));

        $xliffFile = $xliff->appendChild($dom->createElement('file'));
        $xliffFile->setAttribute(
            'id',
            'translation_' . \mb_strtolower(\str_replace('-', '_', $sourceLanguage . '_to_' . $targetLanguage))
        );

        foreach ($data as $id => $translation) {
            $unit = $dom->createElement('unit');
            $unit->setAttribute('id', $id);

            $name = $translation['source'];

            if (\mb_strlen($translation['source']) > 80) {
                $name = \mb_substr(\md5($id), -7);
            }

            $unit->setAttribute('name', $name);

            if (isset($translation['notes'])) {
                $notesElement = $dom->createElement('notes');

                foreach ((array) $translation['notes'] as $note) {
                    $noteElement = $dom->createElement('note');
                    $noteElement->appendChild($dom->createTextNode($note['content'] ?? ''));

                    unset($note['content']);

                    foreach ((array) $note as $name => $value) {
                        $noteElement->setAttribute($name, $value);
                    }

                    $notesElement->appendChild($noteElement);
                }

                $unit->appendChild($notesElement);
            }

            $segmentElement = $unit->appendChild($dom->createElement('segment'));
            $source         = $segmentElement->appendChild($dom->createElement('source'));
            $source->appendChild($dom->createTextNode($translation['source']));

            $targetElement = $dom->createElement('target');

            if (isset($translation['target-attributes'])) {
                foreach ($translation['target-attributes'] as $key => $value) {
                    $targetElement->setAttribute($key, $value);
                }
            }

            $target = $segmentElement->appendChild($targetElement);

            // Does the target contain characters requiring a CDATA section?
            if (\preg_match('/[&<>]/', $translation['target']) === 1) {
                $target->appendChild($dom->createCDATASection($translation['target']));
            } else {
                $target->appendChild($dom->createTextNode($translation['target']));
            }

            $xliffFile->appendChild($unit);
        }

        return $dom->saveXML();
    }
}


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

class TaggableParser extends Parser
{
    /**
     * Tag delimiter.
     *
     * @var string
     */
    public const TAG_DELIMITER = '::';

    /**
     * Key for tagging.
     *
     * @var string
     */
    private $tagKey;

    /**
     * Set tag key.
     *
     * @param string $key
     *
     * @return $this
     */
    public function setTag(string $key): self
    {
        $this->tagKey = $key;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        if (! $this->tagKey) {
            // @var $method self
            return parent::parse($payload);
        }

        return $this->tag($this->tagKey, parent::parse($payload));
    }

    /**
     * Tag all keys with given tag.
     *
     * @param string $tag
     * @param array  $data
     *
     * @return array
     */
    protected function tag(string $tag, array $data): array
    {
        $taggedData = [];

        foreach ($data as $key => $value) {
            $name = \sprintf(
                '%s' . self::TAG_DELIMITER . '%s',
                $tag,
                $key
            );

            $taggedData[$name] = $value;
        }

        return $taggedData;
    }
}


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

use DOMDocument;
use Viserio\Component\Contract\Parser\Dumper as DumperContract;

/**
 * For more infos.
 *
 * @see http://doc.qt.io/qt-5/linguist-ts-file-format.html
 * @see http://svn.ez.no/svn/ezcomponents/trunk/Translation/docs/linguist-format.txt
 */
class QtDumper implements DumperContract
{
    /**
     * {@inheritdoc}
     *
     * array['name']                    string      the id to group translation and to create the name element.
     *     array[]
     *          ['source']              string
     *          array['translation']
     *              array['content']    string      content of the translation element
     *              array['attributes'] false|array attributes for the translation element; simple key value array
     */
    public function dump(array $data): string
    {
        $dom               = new DOMDocument('1.0', 'utf-8');
        $dom->formatOutput = true;

        $ts = $dom->appendChild($dom->createElement('TS'));

        foreach ($data as $name => $groups) {
            $context = $ts->appendChild($dom->createElement('context'));
            $context->appendChild($dom->createElement('name', $name));

            foreach ($groups as $key => $value) {
                $message = $context->appendChild($dom->createElement('message'));
                $message->appendChild($dom->createElement('source', $value['source']));

                $translation = $dom->createElement('translation', $value['translation']['content']);
                $attributes  = $value['translation']['attributes'];

                if (\is_array($attributes)) {
                    foreach ($attributes as $k => $v) {
                        $translation->setAttribute($k, $v);
                    }
                }

                $message->appendChild($translation);
            }
        }

        return $dom->saveXML();
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Viserio\Component\Contract\Events\EventManager as EventManagerContract;
use Viserio\Component\Log\LogManager;

class LoggerServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [
            LogManager::class => [self::class, 'createLogManger'],
            'log'             => static function (ContainerInterface $container) {
                return $container->get(LogManager::class);
            },
            LoggerInterface::class => static function (ContainerInterface $container) {
                return $container->get(LogManager::class)->getDriver();
            },
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [];
    }

    /**
     * Create a log manager instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Log\LogManager
     */
    public static function createLogManger(ContainerInterface $container): LogManager
    {
        $manager = new LogManager($container->get('config'));

        if ($container->has(EventManagerContract::class)) {
            $manager->setEventManager($container->get(EventManagerContract::class));
        }

        return $manager;
    }
}


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

use Closure;

/**
 * @method null|array|string option($key = null)
 * @method void              comment(string $string, $verbosityLevel = null)
 * @method void              line(string $string, ?string $style = null, $verbosityLevel = null)
 */
trait ConfirmableTrait
{
    /**
     * Confirm before proceeding with the action.
     *
     * @param string             $warning
     * @param null|bool|\Closure $callback
     *
     * @return bool
     */
    public function confirmToProceed(string $warning = 'Application is in Production mode!', $callback = null): bool
    {
        $callback      = $callback ?? $this->getDefaultConfirmCallback();
        $shouldConfirm = $callback instanceof Closure ? $callback() : $callback;

        if ($shouldConfirm) {
            if ($this->option('force')) {
                return true;
            }

            $this->comment(\str_repeat('*', \mb_strlen($warning) + 12));
            $this->comment('*     ' . $warning . '     *');
            $this->comment(\str_repeat('*', \mb_strlen($warning) + 12));
            $this->line('');

            $confirmed = $this->confirm('Do you really wish to run this command?');

            if (! $confirmed) {
                $this->comment('Command Cancelled!');

                return false;
            }
        }

        return true;
    }

    /**
     * Confirm a question with the user.
     *
     * @param string $question
     * @param bool   $default
     *
     * @return bool|string
     */
    abstract public function confirm(string $question, bool $default = false);

    /**
     * Get the default confirmation callback.
     *
     * @return \Closure
     */
    protected function getDefaultConfirmCallback(): Closure
    {
        return function () {
            if ($this->container !== null && $this->container->has('env')) {
                return $this->container->get('env') === 'prod';
            }

            if (($env = \getenv('APP_ENV')) !== false) {
                return $env === 'prod';
            }

            return true;
        };
    }
}


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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use RuntimeException;
use Throwable;
use Viserio\Component\Contract\Cache\Traits\CacheItemPoolAwareTrait;
use Viserio\Component\Contract\Events\Traits\EventManagerAwareTrait;
use Viserio\Component\Contract\HttpFactory\Traits\StreamFactoryAwareTrait;
use Viserio\Component\Contract\Profiler\AssetsRenderer as AssetsRendererContract;
use Viserio\Component\Contract\Profiler\DataCollector as DataCollectorContract;
use Viserio\Component\Contract\Profiler\Profiler as ProfilerContract;
use Viserio\Component\Contract\Routing\UrlGenerator as UrlGeneratorContract;
use Viserio\Component\Support\Http\ClientIp;

class Profiler implements ProfilerContract, LoggerAwareInterface
{
    use CacheItemPoolAwareTrait;
    use EventManagerAwareTrait;
    use LoggerAwareTrait;
    use StreamFactoryAwareTrait;

    /**
     * All registered data collectors.
     *
     * @var array
     */
    protected $collectors = [];

    /**
     * A server request instance.
     *
     * @var \Psr\Http\Message\ServerRequestInterface
     */
    protected $serverRequest;

    /**
     * Url generator instance.
     *
     * @var \Viserio\Component\Contract\Routing\UrlGenerator
     */
    protected $urlGenerator;

    /**
     * Assets renderer instance.
     *
     * @var \Viserio\Component\Contract\Profiler\AssetsRenderer
     */
    protected $assetsRenderer;

    /**
     * Enables or disables the profiler.
     *
     * @var bool
     */
    protected $enabled = false;

    /**
     * Template for Profiler.
     *
     * @var string
     */
    protected $template = __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'views' . \DIRECTORY_SEPARATOR . 'profiler.html.php';

    /**
     * Create new Profiler instance.
     *
     * @param \Viserio\Component\Contract\Profiler\AssetsRenderer $assetsRenderer
     */
    public function __construct(AssetsRendererContract $assetsRenderer)
    {
        $this->assetsRenderer = $assetsRenderer->setProfiler($this);
    }

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

    /**
     * {@inheritdoc}
     */
    public function getUrlGenerator(): ?UrlGeneratorContract
    {
        return $this->urlGenerator;
    }

    /**
     * {@inheritdoc}
     */
    public function setUrlGenerator(UrlGeneratorContract $urlGenerator): ProfilerContract
    {
        $this->urlGenerator = $urlGenerator;

        return $this;
    }

    /**
     * Returns a AssetsRenderer for this instance.
     *
     * @return \Viserio\Component\Contract\Profiler\AssetsRenderer
     */
    public function getAssetsRenderer(): AssetsRendererContract
    {
        return $this->assetsRenderer;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setTemplate(string $path): ProfilerContract
    {
        $this->template = $path;

        return $this;
    }

    /**
     * Disables the profiler.
     *
     * @return void
     */
    public function disable(): void
    {
        $this->enabled = false;
    }

    /**
     * Enables the profiler.
     *
     * @return void
     */
    public function enable(): void
    {
        $this->enabled = true;
    }

    /**
     * {@inheritdoc}
     */
    public function addCollector(DataCollectorContract $collector, int $priority = 100): ProfilerContract
    {
        if (isset($this->collectors[$collector->getName()])) {
            throw new RuntimeException(\sprintf('[%s] is already a registered collector.', $collector->getName()));
        }

        $this->collectors[$collector->getName()] = [
            'collector' => $collector,
            'priority'  => $priority,
        ];

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function hasCollector(string $name): bool
    {
        return isset($this->collectors[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function modifyResponse(
        ServerRequestInterface $serverRequest,
        ResponseInterface $response
    ): ResponseInterface {
        if ($this->runningInConsole() || ! $this->enabled) {
            return $response;
        }

        $token = \mb_substr(\hash('sha256', \uniqid((string) \mt_rand(), true)), 0, 6);
        $response->withHeader('x-debug-token', $token);

        try {
            $this->collectData($token, $serverRequest, $response);
        } catch (Throwable $exception) {
            if ($this->logger !== null) {
                $this->logger->error('Profiler exception: ' . $exception->getMessage());
            } else {
                throw $exception;
            }
        }

        if ($this->isHtmlResponse($response)) {
            $response = $this->injectProfiler($response, $token);
        }

        return $response;
    }

    /**
     * {@inheritdoc}
     */
    public function reset(): void
    {
        /** @var \Viserio\Component\Contract\Profiler\DataCollector $collector */
        foreach ($this->collectors as $data) {
            if (isset($data['collector'])) {
                $collector = $data['collector'];

                $collector->reset();
            }
        }
    }

    /**
     * Injects the web debug toolbar into the given Response.
     *
     * @param \Psr\Http\Message\ResponseInterface $response
     * @param string                              $token
     *
     * @return \Psr\Http\Message\ResponseInterface
     *
     * @see https://github.com/symfony/ProfilerBundle/blob/master/EventListener/WebDebugToolbarListener.php
     */
    protected function injectProfiler(ResponseInterface $response, string $token): ResponseInterface
    {
        $content         = (string) $response->getBody();
        $renderedContent = $this->createTemplate($token);

        $pos = \mb_strripos($content, '</body>');

        if ($pos !== false) {
            $stream = $this->streamFactory->createStream(
                \mb_substr($content, 0, $pos) . $renderedContent . \mb_substr($content, $pos)
            );

            // Update the new content and reset the content length
            $response = $response->withoutHeader('content-length');

            return $response->withBody($stream);
        }

        $response->getBody()->write($renderedContent);

        return $response;
    }

    /**
     * Determine if we are running in the console.
     *
     * @return bool
     *
     * @codeCoverageIgnore
     */
    protected function runningInConsole(): bool
    {
        return \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true);
    }

    /**
     * Collect data and create a new profile and save it.
     *
     * @param string                                   $token
     * @param \Psr\Http\Message\ServerRequestInterface $serverRequest
     * @param \Psr\Http\Message\ResponseInterface      $response
     *
     * @return void
     */
    private function collectData(
        string $token,
        ServerRequestInterface $serverRequest,
        ResponseInterface $response
    ): void {
        // sort on priority
        \usort($this->collectors, static function ($a, $b) {
            return $a['priority'] <=> $b['priority'];
        });

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

            $this->collectors[$name]['collector'] = $collector['collector'];
        }

        $ip = (new ClientIp($serverRequest))->getIpAddress();

        if ($this->cachePool !== null) {
            $this->createProfile(
                $token,
                $ip ?? 'Unknown',
                $serverRequest->getMethod(),
                (string) $serverRequest->getUri(),
                $response->getStatusCode(),
                \microtime(true),
                \date('Y-m-d H:i:s'),
                $this->collectors
            );
        }
    }

    /**
     * Create template.
     *
     * @param string $token
     *
     * @return string
     */
    private function createTemplate(string $token): string
    {
        $assets   = $this->getAssetsRenderer();
        $template = new TemplateManager(
            $this->collectors,
            $this->template,
            $token,
            $assets->getIcons()
        );

        return $assets->render() . $template->render();
    }

    /**
     * Check if response is a html response.
     *
     * @param \Psr\Http\Message\ResponseInterface $response
     *
     * @return bool
     */
    private function isHtmlResponse(ResponseInterface $response): bool
    {
        return \mb_strpos($response->getHeaderLine('Content-Type'), 'html', 0, 'UTF-8') !== false;
    }

    /**
     * Create profile with all datas.
     *
     * @param string $token
     * @param string $ip
     * @param string $method
     * @param string $url
     * @param int    $statusCode
     * @param float  $time
     * @param string $date
     * @param array  $collectors
     *
     * @return void
     */
    private function createProfile(
        string $token,
        string $ip,
        string $method,
        string $url,
        int $statusCode,
        float $time,
        string $date,
        array $collectors
    ): void {
        $profile = new Profile($token);
        $profile->setIp($ip);
        $profile->setMethod($method);
        $profile->setUrl($url);
        $profile->setTime($time);
        $profile->setDate($date);
        $profile->setStatusCode($statusCode);
        $profile->setCollectors($collectors);

        $item = $this->cachePool->getItem($token);
        $item->set($profile);
        $item->expiresAfter(60);

        $this->cachePool->save($item);
    }
}


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

use Monolog\Logger as MonologLogger;
use Viserio\Component\Contract\Log\Exception\InvalidArgumentException;

trait ParseLevelTrait
{
    /**
     * All of the error levels.
     *
     * @var array
     */
    protected static $levels = [
        'debug'     => MonologLogger::DEBUG,
        'info'      => MonologLogger::INFO,
        'notice'    => MonologLogger::NOTICE,
        'warning'   => MonologLogger::WARNING,
        'error'     => MonologLogger::ERROR,
        'critical'  => MonologLogger::CRITICAL,
        'alert'     => MonologLogger::ALERT,
        'emergency' => MonologLogger::EMERGENCY,
    ];

    /**
     * Parse the string level into a Monolog constant.
     *
     * @param string $level
     *
     * @throws \InvalidArgumentException
     *
     * @return int
     */
    public static function parseLevel(string $level): int
    {
        if (isset(self::$levels[$level])) {
            return self::$levels[$level];
        }

        throw new InvalidArgumentException('Invalid log level.');
    }
}


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

use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor;
use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Server\DumpServer;
use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Contract\WebServer\Exception\RuntimeException;
use Viserio\Component\Support\Debug\HtmlDumper;

final class ServerDumpCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'server:dump';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'server:dump
        [-H|--host=127.0.0.1 : The hostname to listen to.]
        [-p|--port=8000 : The port to listen to.]
        [--format=cli : The output format.]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Starts a dump server that collects and displays dumps in a single place.';

    /**
     * @var \Symfony\Component\VarDumper\Server\DumpServer
     */
    private $server;

    /**
     * @var \Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface[]
     */
    private $descriptors;

    /**
     * Create a new DumpServerCommand instance.
     *
     * @param \Symfony\Component\VarDumper\Server\DumpServer $server
     */
    public function __construct(DumpServer $server)
    {
        $this->server      = $server;
        $this->descriptors = [
            'cli'  => new CliDescriptor(new CliDumper()),
            'html' => new HtmlDescriptor(new HtmlDumper()),
        ];

        parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    public function handle(): int
    {
        $format     = $this->option('format');
        $output     = $this->getOutput();

        if (! $descriptor = $this->descriptors[$format] ?? null) {
            throw new RuntimeException(\sprintf('Unsupported format [%s].', $format));
        }

        $this->error('Symfony Var Dumper Server');

        $this->server->start();

        $output->success(\sprintf('Server listening on %s', $this->server->getHost()));

        $this->comment('Quit the server with CONTROL-C.');

        $this->server->listen(static function (Data $data, array $context, int $clientId) use ($descriptor, $output): void {
            $descriptor->describe($output, $data, $context, $clientId);
        });

        return 0;
    }
}


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

use Cake\Chronos\Chronos;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Viserio\Component\Contract\Session\Store as StoreContract;
use Viserio\Component\Cookie\RequestCookies;
use Viserio\Component\Cookie\SetCookie;
use Viserio\Component\Session\Fingerprint\ClientIpGenerator;
use Viserio\Component\Session\Fingerprint\UserAgentGenerator;
use Viserio\Component\Session\Handler\CookieSessionHandler;
use Viserio\Component\Session\SessionManager;

class StartSessionMiddleware implements MiddlewareInterface
{
    /**
     * The session manager.
     *
     * @var \Viserio\Component\Session\SessionManager
     */
    protected $manager;

    /**
     * Driver config.
     *
     * @var array
     */
    protected $driverConfig = [];

    /**
     * Cookie config from session manager.
     *
     * @var array
     */
    protected $cookieConfig = [];

    /**
     * Session cookie lifetime.
     *
     * @var int
     */
    protected $lifetime;

    /**
     * List of fingerprint generators.
     *
     * @var array
     */
    private $fingerprintGenerators = [
        ClientIpGenerator::class,
        UserAgentGenerator::class,
    ];

    /**
     * Create a new session middleware.
     *
     * @param \Viserio\Component\Session\SessionManager $manager
     */
    public function __construct(SessionManager $manager)
    {
        $this->manager      = $manager;
        $this->driverConfig = $manager->getDriverConfig($manager->getDefaultDriver());
        $this->lifetime     = $manager->getConfig()['lifetime'];
        $this->cookieConfig = $manager->getConfig()['cookie'];
    }

    /**
     * {@inheritdoc}
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // We need to start the session so that the data is ready.
        // Note that the Narrowspark sessions do not make use of PHP
        // "native" sessions in any way since they are crappy.
        $session = $this->startSession($request);

        $request = $request->withAttribute('session', $session);

        $this->collectGarbage($session);

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

        // Again, if the session has been configured we will need to close out the session
        // so that the attributes may be persisted to some storage medium. We will also
        // add the session identifier cookie to the application response headers now.
        $session = $this->storeCurrentUrl($request, $session);

        return $this->addCookieToResponse($request, $response, $session);
    }

    /**
     * Start the session for the given request.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function startSession(ServerRequestInterface $request): StoreContract
    {
        $session   = $this->manager->getDriver();
        $cookies   = RequestCookies::fromRequest($request);

        $session->setId($cookies->has($session->getName()) ? $cookies->get($session->getName())->getValue() : '');

        foreach ($this->fingerprintGenerators as $fingerprintGenerator) {
            $session->addFingerprintGenerator(new $fingerprintGenerator($request));
        }

        if ($session->handlerNeedsRequest()) {
            $session->setRequestOnHandler($request);
        }

        if (! $session->open()) {
            $session->start();
        }

        return $session;
    }

    /**
     * Store the current URL for the request if necessary.
     *
     * @param \Psr\Http\Message\ServerRequestInterface  $request
     * @param \Viserio\Component\Contract\Session\Store $session
     *
     * @return \Viserio\Component\Contract\Session\Store
     */
    protected function storeCurrentUrl(ServerRequestInterface $request, StoreContract $session): StoreContract
    {
        if ($request->getMethod() === 'GET' &&
            $request->getHeaderLine('x-requested-with') !== 'XMLHttpRequest'
        ) {
            $session->setPreviousUrl((string) $request->getUri());
        }

        return $session;
    }

    /**
     * Remove the garbage from the session if necessary.
     *
     * @param \Viserio\Component\Contract\Session\Store $session
     *
     * @return void
     */
    protected function collectGarbage(StoreContract $session): void
    {
        $lottery     = $this->cookieConfig['lottery'];
        $hitsLottery = \random_int(1, $lottery[1]) <= $lottery[0];

        // Here we will see if this request hits the garbage collection lottery by hitting
        // the odds needed to perform garbage collection on any given request. If we do
        // hit it, we'll call this handler to let it delete all the expired sessions.
        if ($hitsLottery) {
            $session->getHandler()->gc($this->lifetime);
        }
    }

    /**
     * Add the session cookie to the application response.
     *
     * @param \Psr\Http\Message\ServerRequestInterface  $request
     * @param \Psr\Http\Message\ResponseInterface       $response
     * @param \Viserio\Component\Contract\Session\Store $session
     *
     * @throws \Viserio\Component\Contract\Cookie\Exception\InvalidArgumentException
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function addCookieToResponse(
        ServerRequestInterface $request,
        ResponseInterface $response,
        StoreContract $session
    ): ResponseInterface {
        if ($session->getHandler() instanceof CookieSessionHandler) {
            $session->save();
        }

        $uri    = $request->getUri();

        $setCookie = new SetCookie(
            $session->getName(),
            $session->getId(),
            $this->cookieConfig['expire_on_close'] === true ? 0 : Chronos::now()->addSeconds($this->lifetime),
            $this->cookieConfig['path'] ?? '/',
            $this->cookieConfig['domain'] ?? $uri->getHost(),
            $this->cookieConfig['secure'] ?? ($uri->getScheme() === 'https'),
            $this->cookieConfig['http_only'] ?? true,
            $this->cookieConfig['samesite'] ?? false
        );

        return $response->withAddedHeader('set-cookie', (string) $setCookie);
    }
}


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

use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Events\EventManager;
use Viserio\Component\WebServer\Command\Traits\ServerCommandRequirementsCheckTrait;
use Viserio\Component\WebServer\WebServer;
use Viserio\Component\WebServer\WebServerConfig;

final class ServerStartCommand extends AbstractCommand
{
    use ServerCommandRequirementsCheckTrait;

    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'server:start';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'server:start
        [-H|--host= : The hostname to listen to.]
        [-p|--port= : The port to listen to.]
        [-r|--router= : Path to custom router script.]
        [--pidfile= : PID file.]
        [--disable-xdebug : Disable xdebug on server]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Starts a local web server in the background.';

    /**
     * Create a new ServerStartCommand Instance.
     *
     * @param string $documentRoot
     * @param string $environment
     */
    public function __construct(string $documentRoot, string $environment)
    {
        $this->documentRoot = $documentRoot;
        $this->environment  = $environment;

        parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    public function handle(): int
    {
        if (! \extension_loaded('pcntl')) {
            $this->error('This command needs the pcntl extension to run. You can either install it or use the "server:serve" command instead.');

            if ($this->confirm('Do you want to execute <info>server:serve</info> immediately?', false)) {
                return $this->call('server:serve');
            }

            return 1;
        }

        if ($this->checkRequirements() === 1) {
            return 1;
        }

        // replace event dispatcher with an empty one to prevent console.terminate from firing
        // as container could have changed between start and stop
        if (\class_exists(EventManager::class)) {
            /** @param \Viserio\Component\Console\Application $application */
            $application = $this->getApplication();
            $application->setEventManager(new EventManager());
        }

        try {
            $webServerConfig = new WebServerConfig($this->documentRoot, $this->environment, $this);

            $pidFile = $webServerConfig->getPidFile();

            if (WebServer::isRunning($pidFile)) {
                $this->error(\sprintf(
                    'The web server has already been started. It is currently listening on http://%s. Please stop the web server before you try to start it again.',
                    WebServer::getAddress($pidFile)
                ));

                return 1;
            }

            if (WebServer::STARTED === WebServer::start($webServerConfig, $pidFile)) {
                $output          = $this->getOutput();
                $host            = $webServerConfig->getHostname();
                $port            = $webServerConfig->getPort();
                $resolvedAddress = $webServerConfig->getDisplayAddress();

                $output->success(\sprintf(
                    'Server listening on %s%s',
                    $host !== '0.0.0.0' ? $host . ':' . $port : 'all interfaces, port ' . $port,
                    $resolvedAddress !== null ? \sprintf(' -- see http://%s)', $resolvedAddress) : ''
                ));

                if ($webServerConfig->hasXdebug()) {
                    $output->comment('Xdebug profiler trigger enabled.');
                }
            }

            return 0;
        } catch (\Exception $exception) {
            $this->error($exception->getMessage());

            return 1;
        }
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Twig\Environment;
use Viserio\Bridge\Twig\Command\DebugCommand;
use Viserio\Component\Console\Application;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Contract\View\Finder as FinderContract;
use Viserio\Provider\Twig\Command\CleanCommand;
use Viserio\Provider\Twig\Command\LintCommand;

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:twig'  => LintCommand::class,
                'twig:clear' => CleanCommand::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 && $container->has(Environment::class)) {
            $console->add(new CleanCommand($container->get('config')));

            if (\class_exists(DebugCommand::class)) {
                $twig = $container->get(Environment::class);

                $console->addCommands([
                    new LintCommand($twig, $container->get(FinderContract::class), $container->get('config')),
                ]);
            }
        }

        return $console;
    }
}


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

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Viserio\Component\StaticalProxy\StaticalProxy;
use Viserio\Component\View\ViewFactory;

class View extends StaticalProxy
{
    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public static function getInstanceIdentifier()
    {
        return ViewFactory::class;
    }

    /**
     * Create a response from string.
     *
     * @param string $template
     * @param array  $args
     *
     * @return ResponseInterface
     */
    public static function createResponseView(string $template, array $args = []): ResponseInterface
    {
        $response = self::$container->get(ResponseFactoryInterface::class)->createResponse();
        $response = $response->withAddedHeader('Content-Type', 'text/html');

        $stream = self::$container->get(StreamFactoryInterface::class)->createStream();
        $stream->write((string) self::$container->get(ViewFactory::class)->create($template, $args));

        return $response->withBody($stream);
    }
}


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

use InvalidArgumentException;
use Twig\Error\LoaderError;
use Twig\Loader\ExistsLoaderInterface;
use Twig\Loader\LoaderInterface;
use Twig\Source;
use Viserio\Component\Contract\Filesystem\Exception\FileNotFoundException;
use Viserio\Component\Contract\View\Finder as FinderContract;

class Loader implements LoaderInterface, ExistsLoaderInterface
{
    /**
     * The filesystem instance.
     *
     * @var \Viserio\Component\Contract\Filesystem\Filesystem
     */
    protected $files;

    /**
     * The finder instance.
     *
     * @var \Viserio\Component\Contract\View\Finder
     */
    protected $finder;

    /**
     * Twig file extension.
     *
     * @var string
     */
    protected $extension = 'twig';

    /**
     * Template lookup cache.
     *
     * @var array
     */
    protected $cache = [];

    /**
     * Create a new twig loader instance.
     *
     * @param \Viserio\Component\Contract\View\Finder $finder
     */
    public function __construct(FinderContract $finder)
    {
        $this->files  = $finder->getFilesystem();
        $this->finder = $finder;
    }

    /**
     * Set file extension for the twig loader.
     *
     * @param string $extension
     *
     * @return \Twig\Loader\LoaderInterface
     *
     * @codeCoverageIgnore
     */
    public function setExtension(string $extension): LoaderInterface
    {
        $this->extension = $extension;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function exists($name): bool
    {
        try {
            $this->findTemplate($name);
        } catch (LoaderError $exception) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function getSourceContext($name)
    {
        $template = $this->findTemplate($name);

        try {
            $source = $this->files->read($template);
        } catch (FileNotFoundException $exception) {
            throw new LoaderError(\sprintf('Twig file [%s] was not found.', $exception->getMessage()));
        }

        if ($source === false) {
            throw new LoaderError(\sprintf('A error occurred during template [%s] reading', $name));
        }

        return new Source($source, $name, $template);
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function getCacheKey($name): string
    {
        return $this->findTemplate($name);
    }

    /**
     * {@inheritdoc}
     */
    public function isFresh($name, $time): bool
    {
        return $this->files->getTimestamp($this->findTemplate($name)) <= $time;
    }

    /**
     * Return path to template without the need for the extension.
     *
     * @param string $name
     *
     * @throws \Twig\Error\LoaderError
     *
     * @return string
     */
    public function findTemplate(string $name): string
    {
        if ($this->files->has($name)) {
            return $name;
        }

        $name = $this->normalizeName($name);

        if (isset($this->cache[$name])) {
            return $this->cache[$name];
        }

        try {
            $found              = $this->finder->find($name);
            $this->cache[$name] = $found['path'];
        } catch (InvalidArgumentException $exception) {
            throw new LoaderError($exception->getMessage());
        }

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

    /**
     * Normalize the Twig template name to a name the ViewFinder can use.
     *
     * @param string $name
     *
     * @return string
     */
    protected function normalizeName(string $name): string
    {
        if ($this->files->getExtension($name) === $this->extension) {
            $name = \mb_substr($name, 0, -(\mb_strlen($this->extension) + 1));
        }

        return $name;
    }
}


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

use Psr\Http\Message\StreamInterface;
use RuntimeException as BaseRuntimeException;
use Viserio\Component\Contract\Http\Exception\RuntimeException;
use Viserio\Component\Http\Util;

class PumpStream implements StreamInterface
{
    /**
     * The size of the stream if known.
     *
     * @var null|int
     */
    protected $size;

    /**
     * Source of the stream data.
     *
     * @var null|callable
     */
    private $source;

    /**
     *@var int
     */
    private $tellPos = 0;

    /**
     * Stream metadata.
     *
     * @var array
     */
    private $metadata;

    /**
     * Buffer stream instance.
     *
     * @var \Viserio\Component\Http\Stream\BufferStream
     */
    private $buffer;

    /**
     * Create a new pump stream instance.
     *
     * @param callable $source  Source of the stream data. The callable MAY
     *                          accept an integer argument used to control the
     *                          amount of data to return. The callable MUST
     *                          return a string when called, or false on error
     *                          or EOF.
     * @param array    $options stream options:
     *                          - metadata: Hash of metadata to use with stream.
     *                          - size: Size of the stream, if known
     */
    public function __construct(callable $source, array $options = [])
    {
        $this->source   = $source;
        $this->size     = $options['size']     ?? null;
        $this->metadata = $options['metadata'] ?? [];
        $this->buffer   = new BufferStream();
    }

    /**
     * {@inheritdoc}
     */
    public function __toString(): string
    {
        try {
            return Util::copyToString($this);
        } catch (BaseRuntimeException $exception) {
            // Really, PHP? https://bugs.php.net/bug.php?id=53648
            \trigger_error(self::class . '::__toString exception: ' . (string) $exception, \E_USER_ERROR);

            return '';
        }
    }

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

    /**
     * {@inheritdoc}
     */
    public function getMetadata($key = null)
    {
        if (! $key) {
            return $this->metadata;
        }

        return $this->metadata[$key] ?? null;
    }

    /**
     * {@inheritdoc}
     */
    public function close(): void
    {
        $this->detach();
    }

    /**
     * {@inheritdoc}
     */
    public function detach(): void
    {
        $this->tellPos = 0;
        $this->source  = null;
    }

    /**
     * {@inheritdoc}
     */
    public function tell(): int
    {
        return $this->tellPos;
    }

    /**
     * {@inheritdoc}
     */
    public function eof(): bool
    {
        return ! $this->source;
    }

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

    /**
     * {@inheritdoc}
     */
    public function rewind(): void
    {
        $this->seek(0);
    }

    /**
     * {@inheritdoc}
     */
    public function seek($offset, $whence = \SEEK_SET): void
    {
        throw new RuntimeException('Cannot seek a PumpStream.');
    }

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

    /**
     * {@inheritdoc}
     *
     * @throws \RuntimeException
     *
     * @return void
     */
    public function write($string): void
    {
        throw new RuntimeException('Cannot write to a PumpStream.');
    }

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

    /**
     * {@inheritdoc}
     */
    public function read($length): string
    {
        $data    = $this->buffer->read($length);
        $readLen = \strlen($data);
        $this->tellPos += $readLen;
        $remaining = $length - $readLen;

        if ($remaining) {
            $this->pump($remaining);
            $data .= $this->buffer->read($remaining);
            $this->tellPos += \strlen($data) - $readLen;
        }

        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function getContents(): string
    {
        $result = '';

        while (! $this->eof()) {
            $result .= $this->read(1000000);
        }

        return $result;
    }

    /**
     * @param int $length
     *
     * @return null|void
     */
    private function pump($length): void
    {
        if ($this->source) {
            do {
                $data = \call_user_func($this->source, $length);

                if ($data === false || $data === null) {
                    $this->source = null;

                    return;
                }

                $this->buffer->write($data);

                $length -= \strlen($data);
            } while ($length > 0);
        }
    }
}


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

use Psr\Http\Message\StreamInterface;
use RuntimeException as BaseRuntimeException;
use Viserio\Component\Contract\Http\Exception\RuntimeException;

/**
 * Provides a buffer stream that can be written to to fill a buffer, and read
 * from to remove bytes from the buffer.
 *
 * This stream returns a "hwm" metadata value that tells upstream consumers
 * what the configured high water mark of the stream is, or the maximum
 * preferred size of the buffer.
 */
class BufferStream implements StreamInterface
{
    /**
     * High water mark.
     *
     * @var int
     */
    private $hwm;

    /**
     * Buffer size.
     *
     * @var string
     */
    private $buffer = '';

    /**
     * Create a new buffer stream instance.
     *
     * @param int $hwm High water mark, representing the preferred maximum
     *                 buffer size. If the size of the buffer exceeds the high
     *                 water mark, then calls to write will continue to succeed
     *                 but will return false to inform writers to slow down
     *                 until the buffer has been drained by reading from it.
     */
    public function __construct(int $hwm = 16384)
    {
        $this->hwm = $hwm;
    }

    /**
     * {@inheritdoc}
     */
    public function __toString(): string
    {
        try {
            return $this->getContents();
        } catch (BaseRuntimeException $exception) {
            // Really, PHP? https://bugs.php.net/bug.php?id=5364
            \trigger_error(self::class . '::__toString exception: ' . (string) $exception, \E_USER_ERROR);

            return '';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getContents(): string
    {
        $buffer       = $this->buffer;
        $this->buffer = '';

        return $buffer;
    }

    /**
     * {@inheritdoc}
     */
    public function close(): void
    {
        $this->buffer = '';
    }

    /**
     * {@inheritdoc}
     */
    public function detach(): void
    {
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    public function getSize()
    {
        return \strlen($this->buffer);
    }

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

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

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

    /**
     * {@inheritdoc}
     */
    public function rewind(): void
    {
        $this->seek(0);
    }

    /**
     * {@inheritdoc}
     */
    public function seek($offset, $whence = \SEEK_SET): void
    {
        throw new RuntimeException('Cannot seek a BufferStream.');
    }

    /**
     * {@inheritdoc}
     */
    public function eof(): bool
    {
        return $this->buffer === '';
    }

    /**
     * {@inheritdoc}
     */
    public function tell(): void
    {
        throw new RuntimeException('Cannot determine the position of a BufferStream.');
    }

    /**
     * {@inheritdoc}
     */
    public function read($length): string
    {
        $currentLength = \strlen($this->buffer);

        if ($length >= $currentLength) {
            // No need to slice the buffer because we don't have enough data.
            $result       = $this->buffer;
            $this->buffer = '';
        } else {
            // Slice up the result to provide a subset of the buffer.
            $result       = \substr($this->buffer, 0, $length);
            $this->buffer = \substr($this->buffer, $length);
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function write($string): int
    {
        $this->buffer .= $string;

        if (\strlen($this->buffer) >= $this->hwm) {
            return 0;
        }

        return \strlen($string);
    }

    /**
     * {@inheritdoc}
     */
    public function getMetadata($key = null)
    {
        if ($key === 'hwm') {
            return $this->hwm;
        }

        return $key ? null : [];
    }
}


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

use League\Flysystem\AdapterInterface;
use League\Flysystem\WebDAV\WebDAVAdapter;
use Sabre\DAV\Client;
use Viserio\Component\Contract\Filesystem\Connector as ConnectorContract;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresConfig as RequiresConfigContract;
use Viserio\Component\Contract\OptionsResolver\RequiresMandatoryOptions as RequiresMandatoryOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresValidatedConfig as RequiresValidatedConfigContract;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

final class WebDavConnector implements
    ConnectorContract,
    RequiresConfigContract,
    ProvidesDefaultOptionsContract,
    RequiresMandatoryOptionsContract,
    RequiresValidatedConfigContract
{
    use OptionsResolverTrait;

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

    /**
     * Create a new AwsS3Connector instance.
     *
     * @param array $config
     */
    public function __construct(array $config)
    {
        $this->resolvedOptions = self::resolveOptions($config);
    }

    /**
     * {@inheritdoc}
     */
    public static function getMandatoryOptions(): array
    {
        return [
            'auth' => [
                'baseUri',
            ],
        ];
    }

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

    /**
     * {@inheritdoc}
     */
    public static function getOptionValidators(): array
    {
        return [
            'auth' => [
                'baseUri' => ['string'],
            ],
            'prefix'              => ['string', 'null'],
            'use_streamed_copy'   => ['bool'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function connect(): AdapterInterface
    {
        return new WebDAVAdapter(
            new Client($this->resolvedOptions['auth']),
            $this->resolvedOptions['prefix'],
            $this->resolvedOptions['use_streamed_copy']
        );
    }
}


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

use Closure;
use InvalidArgumentException;
use Spatie\Macroable\Macroable;
use Swift_Mailer;
use Swift_Mime_SimpleMessage;
use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Events\Traits\EventManagerAwareTrait;
use Viserio\Component\Contract\Mail\Exception\UnexpectedValueException;
use Viserio\Component\Contract\Mail\Mailer as MailerContract;
use Viserio\Component\Contract\Mail\Message as MessageContract;
use Viserio\Component\Contract\View\Traits\ViewAwareTrait;
use Viserio\Component\Mail\Event\MessageSendingEvent;
use Viserio\Component\Mail\Event\MessageSentEvent;
use Viserio\Component\Support\Traits\InvokerAwareTrait;

class Mailer implements MailerContract
{
    use ContainerAwareTrait;
    use EventManagerAwareTrait;
    use InvokerAwareTrait;
    use Macroable;
    use ViewAwareTrait;

    /**
     * The Swift Mailer instance.
     *
     * @var \Swift_Mailer
     */
    protected $swift;

    /**
     * The global from address and name.
     *
     * @var array
     */
    protected $from = [];

    /**
     * Set the global to address and name.
     *
     * @var array
     */
    protected $to = [];

    /**
     * The global reply-to address and name.
     *
     * @var array
     */
    protected $replyTo;

    /**
     * Array of failed recipients.
     *
     * @var array
     */
    protected $failedRecipients = [];

    /**
     * Resolved options.
     *
     * @var array
     */
    protected $options;

    /**
     * Create a new Mailer instance.
     *
     * @param \Swift_Mailer $swiftMailer
     * @param array         $data
     */
    public function __construct(Swift_Mailer $swiftMailer, array $data)
    {
        $this->options = $data;

        // If a "from" address is set, we will set it on the mailer so that all mail
        // messages sent by the applications will utilize the same "from" address
        // on each one, which makes the developer's life a lot more convenient.
        $from = $this->options['from'] ?? null;

        if (\is_array($from) && isset($from['address'], $from['name'])) {
            $this->alwaysFrom($from['address'], $from['name']);
        }

        $to = $this->options['to'] ?? null;

        if (\is_array($to) && isset($to['address'], $to['name'])) {
            $this->alwaysTo($to['address'], $to['name']);
        }

        $this->swift = $swiftMailer;
    }

    /**
     * {@inheritdoc}
     */
    public function alwaysFrom(string $address, string $name = null): void
    {
        $this->from = \compact('address', 'name');
    }

    /**
     * {@inheritdoc}
     */
    public function alwaysTo(string $address, string $name = null): void
    {
        $this->to = \compact('address', 'name');
    }

    /**
     * Set the global reply-to address and name.
     *
     * @param string      $address
     * @param null|string $name
     *
     * @return void
     */
    public function alwaysReplyTo(string $address, $name = null): void
    {
        $this->replyTo = \compact('address', 'name');
    }

    /**
     * {@inheritdoc}
     */
    public function raw(string $text, $callback): int
    {
        return $this->send(['raw' => $text], [], $callback);
    }

    /**
     * {@inheritdoc}
     */
    public function plain(string $view, array $data, $callback): int
    {
        return $this->send(['text' => $view], $data, $callback);
    }

    /**
     * {@inheritdoc}
     */
    public function send($view, array $data = [], $callback = null): int
    {
        // First we need to parse the view, which could either be a string or an array
        // containing both an HTML and plain text versions of the view which should
        // be used when sending an e-mail. We will extract both of them out here.
        [$view, $plain, $raw] = $this->parseView($view);

        $data['message'] = $message = $this->createMessage();

        // Once we have retrieved the view content for the e-mail we will set the body
        // of this message using the HTML type, which will provide a simple wrapper
        // to creating view based emails that are able to receive arrays of data.
        $this->addContent($message, $view, $plain, $raw, $data);

        if ($callback !== null) {
            $this->callMessageBuilder($callback, $message);
        }

        // If a global to address has been specified we will override
        // any recipient addresses previously set and use this one instead.
        if (isset($this->to['address'])) {
            $message->to($this->to['address'], $this->to['name'], true);
            $message->cc(null, null, true);
            $message->bcc(null, null, true);
        }

        $recipients = $this->sendSwiftMessage($message->getSwiftMessage());

        if ($this->eventManager !== null) {
            $this->eventManager->trigger(new MessageSentEvent($this, $message->getSwiftMessage(), $recipients));
        }

        return $recipients;
    }

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

    /**
     * Get the Swift Mailer instance.
     *
     * @return \Swift_Mailer
     */
    public function getSwiftMailer(): Swift_Mailer
    {
        return $this->swift;
    }

    /**
     * Parse the given view name or array.
     *
     * @param array|string $view
     *
     * @throws \Viserio\Component\Contract\Mail\Exception\UnexpectedValueException
     *
     * @return array
     */
    protected function parseView($view): array
    {
        if (\is_string($view)) {
            return [$view, null, null];
        }

        // If the given view is an array with numeric keys, we will just assume that
        // both a "pretty" and "plain" view were provided, so we will return this
        // array as is, since must should contain both views with numeric keys.
        if (\is_array($view) && isset($view[0])) {
            return [$view[0], $view[1], null];
        }

        // If the view is an array, but doesn't contain numeric keys, we will assume
        // the the views are being explicitly specified and will extract them via
        // named keys instead, allowing the developers to use one or the other.
        if (\is_array($view)) {
            return [
                $view['html'] ?? null,
                $view['text'] ?? null,
                $view['raw'] ?? null,
            ];
        }

        throw new UnexpectedValueException('Invalid view.');
    }

    /**
     * Add the content to a given message.
     *
     * @param \Viserio\Component\Contract\Mail\Message $message
     * @param null|string                              $view
     * @param null|string                              $plain
     * @param null|string                              $raw
     * @param array                                    $data
     */
    protected function addContent(
        MessageContract $message,
        ?string $view,
        ?string $plain,
        ?string $raw,
        array $data
    ): void {
        if ($view !== null) {
            $message->setBody($this->createView($view, $data), 'text/html');
        }

        if ($plain !== null) {
            $method = $view !== null ? 'addPart' : 'setBody';

            $message->{$method}($this->createView($plain, $data), 'text/plain');
        }

        if ($raw !== null) {
            $method = ($view !== null || $plain !== null) ? 'addPart' : 'setBody';

            $message->{$method}($raw, 'text/plain');
        }
    }

    /**
     * Send a Swift Message instance.
     *
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return int
     */
    protected function sendSwiftMessage(Swift_Mime_SimpleMessage $message): int
    {
        if (! $this->shouldSendMessage($message)) {
            return 0;
        }

        try {
            return $this->swift->send($message, $this->failedRecipients);
        } finally {
            $this->forceReconnecting();
        }
    }

    /**
     * Determines if the message can be sent.
     *
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return bool
     */
    protected function shouldSendMessage(Swift_Mime_SimpleMessage $message): bool
    {
        if (! $this->eventManager) {
            return true;
        }

        return $this->eventManager->trigger(new MessageSendingEvent($this, $message)) !== false;
    }

    /**
     * Force the transport to re-connect.
     *
     * This will prevent errors in daemon queue situations.
     *
     * @return void
     */
    protected function forceReconnecting(): void
    {
        $this->swift->getTransport()->stop();
    }

    /**
     * Create a new message instance.
     *
     * @return \Viserio\Component\Contract\Mail\Message
     */
    protected function createMessage(): MessageContract
    {
        $message = new Message($this->swift->createMessage());

        // If a global from address has been specified we will set it on every message
        // instances so the developer does not have to repeat themselves every time
        // they create a new message. We will just go ahead and push the address.
        if (isset($this->from['address'])) {
            $message->from($this->from['address'], $this->from['name']);
        }

        // When a global reply address was specified we will set this on every message
        // instance so the developer does not have to repeat themselves every time
        // they create a new message. We will just go ahead and push this address.
        if (! empty($this->replyTo['address'])) {
            $message->replyTo($this->replyTo['address'], $this->replyTo['name']);
        }

        return $message;
    }

    /**
     * Call the provided message builder.
     *
     * @param null|\Closure|string                     $callback
     * @param \Viserio\Component\Contract\Mail\Message $message
     *
     * @throws \InvalidArgumentException
     *
     * @return mixed
     */
    protected function callMessageBuilder($callback, MessageContract $message)
    {
        if ($callback instanceof Closure) {
            return $callback($message);
        }

        if ($this->container !== null) {
            return $this->getInvoker()->call($callback)->mail($message);
        }

        throw new InvalidArgumentException('Callback is not valid.');
    }

    /**
     * Creates a view string for the email body.
     *
     * @param string $view
     * @param array  $data
     *
     * @return string
     */
    protected function createView(string $view, array $data): string
    {
        if ($this->viewFactory !== null) {
            return $this->viewFactory->create($view, $data)->render();
        }

        return \vsprintf($view, $data);
    }
}