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

use Cake\Chronos\Chronos;
use Closure;
use Cron\CronExpression;
use Spatie\Macroable\Macroable;
use Symfony\Component\Process\Process;
use Viserio\Component\Contract\Cache\Traits\CacheItemPoolAwareTrait;
use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Cron\Cron as CronContract;
use Viserio\Component\Support\Traits\InvokerAwareTrait;

class Cron implements CronContract
{
    use ContainerAwareTrait;
    use CacheItemPoolAwareTrait;
    use InvokerAwareTrait;
    use Macroable;

    /**
     * The cron expression representing the cron job's frequency.
     *
     * @var string
     */
    protected $expression = '* * * * *';

    /**
     * The command string.
     *
     * @var string
     */
    protected $command;

    /**
     * The array of filter callbacks.
     *
     * @var array
     */
    protected $filters = [];

    /**
     * The array of reject callbacks.
     *
     * @var array
     */
    protected $rejects = [];

    /**
     * The location that output should be sent to.
     *
     * @var string
     */
    protected $output;

    /**
     * Indicates whether output should be appended.
     *
     * @var bool
     */
    protected $shouldAppendOutput = false;

    /**
     * The array of callbacks to be run before the cron is started.
     *
     * @var array
     */
    protected $beforeCallbacks = [];

    /**
     * The array of callbacks to be run after the cron is finished.
     *
     * @var array
     */
    protected $afterCallbacks = [];

    /**
     * The human readable description of the cron.
     *
     * @var null|string
     */
    protected $description;

    /**
     * The timezone the date should be evaluated on.
     *
     * @var string
     */
    protected $timezone;

    /**
     * The list of environments the command should run under.
     *
     * @var array
     */
    protected $environments = [];

    /**
     * Path for the working directory.
     *
     * @var string
     */
    protected $path;

    /**
     * Indicates if the command should run in background.
     *
     * @var bool
     */
    protected $runInBackground = false;

    /**
     * The user the command should run as.
     *
     * @var string
     */
    protected $user;

    /**
     * Indicates if the command should run in maintenance mode.
     *
     * @var bool
     */
    protected $evenInMaintenanceMode = false;

    /**
     * Indicates if the command should not overlap itself.
     *
     * @var bool
     */
    protected $withoutOverlapping = false;

    /**
     * Create a new cron instance.
     *
     * @param string $command
     */
    public function __construct(string $command)
    {
        $this->command = $command;
        $this->output  = $this->getDefaultOutput();
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function setDescription(string $description): CronContract
    {
        $this->description = $description;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setTimezone(string $timezone): CronContract
    {
        $this->timezone = $timezone;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setEnvironments($environments): CronContract
    {
        $this->environments = \is_array($environments) ? $environments : \func_get_args();

        return $this;
    }

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

    /**
     * Set working directory.
     *
     * @param string $path
     *
     * @return $this
     */
    public function setPath(string $path): self
    {
        $this->path = $path;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function runInBackground(): CronContract
    {
        $this->runInBackground = true;

        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setUser(string $user): CronContract
    {
        $this->user = $user;

        return $this;
    }

    /**
     * State that the cron job should run even in maintenance mode.
     *
     * @return $this
     */
    public function evenInMaintenanceMode(): self
    {
        $this->evenInMaintenanceMode = true;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function withoutOverlapping(): CronContract
    {
        $this->withoutOverlapping = true;

        $this->after(function (): void {
            $this->cachePool->deleteItem($this->getMutexName());
        })->skip(function () {
            return $this->cachePool->hasItem($this->getMutexName());
        });

        return $this;
    }

    /**
     * Determine if the cron job runs in maintenance mode.
     *
     * @return bool
     */
    public function runsInMaintenanceMode(): bool
    {
        return $this->evenInMaintenanceMode;
    }

    /**
     * {@inheritdoc}
     */
    public function runsInEnvironment(string $environment): bool
    {
        return \count($this->environments) === 0 || \in_array($environment, $this->environments, true);
    }

    /**
     * {@inheritdoc}
     */
    public function run()
    {
        if ($this->withoutOverlapping) {
            $item = $this->cachePool->getItem($this->getMutexName());
            $item->set($this->getMutexName());
            $item->expiresAfter(1440);

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

        if (! $this->runInBackground) {
            return $this->runCommandInForeground();
        }

        $run = $this->runCommandInBackground();

        if ($this->withoutOverlapping) {
            $this->cachePool->deleteItem($this->getMutexName());
        }

        return $run;
    }

    /**
     * {@inheritdoc}
     */
    public function buildCommand(): string
    {
        $output   = \escapeshellarg($this->output);
        $redirect = $this->shouldAppendOutput ? ' >> ' : ' > ';
        $command  = $this->command . $redirect . $output . ($this->isWindows() ? ' 2>&1' : ' 2>&1 &');

        return $this->ensureCorrectUser($command);
    }

    /**
     * {@inheritdoc}
     */
    public function sendOutputTo(string $location): CronContract
    {
        $this->output = $location;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function appendOutputTo(string $location): CronContract
    {
        $this->output             = $location;
        $this->shouldAppendOutput = true;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function isDue(string $environment, bool $isMaintenance = false): bool
    {
        if (! $this->runsInMaintenanceMode() && $isMaintenance) {
            return false;
        }

        return $this->expressionPasses() && $this->runsInEnvironment($environment);
    }

    /**
     * {@inheritdoc}
     */
    public function cron(string $expression): CronContract
    {
        $this->expression = $expression;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function hourly(): CronContract
    {
        $this->spliceIntoPosition(1, 0);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function daily(): CronContract
    {
        $this->spliceIntoPosition(1, 0)
            ->spliceIntoPosition(2, 0);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function monthly(): CronContract
    {
        $this->spliceIntoPosition(1, 0)
            ->spliceIntoPosition(2, 0)
            ->spliceIntoPosition(3, 1);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function yearly(): CronContract
    {
        $this->spliceIntoPosition(1, 0)
            ->spliceIntoPosition(2, 0)
            ->spliceIntoPosition(3, 1)
            ->spliceIntoPosition(4, 1);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function quarterly(): CronContract
    {
        $this->spliceIntoPosition(1, 0)
            ->spliceIntoPosition(2, 0)
            ->spliceIntoPosition(3, 1)
            ->spliceIntoPosition(4, '*/3');

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function everyMinute(): CronContract
    {
        $this->spliceIntoPosition(1, '*');

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function everyFiveMinutes(): CronContract
    {
        $this->spliceIntoPosition(1, '*/5');

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function everyTenMinutes(): CronContract
    {
        $this->spliceIntoPosition(1, '*/10');

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function everyFifteenMinutes(): CronContract
    {
        $this->spliceIntoPosition(1, '*/15');

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function everyThirtyMinutes(): CronContract
    {
        $this->spliceIntoPosition(1, '0,30');

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function days($days): CronContract
    {
        $days = \is_array($days) ? $days : \func_get_args();

        $this->spliceIntoPosition(5, \implode(',', $days));

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function monthlyOn(int $day = 1, string $time = '0:0'): CronContract
    {
        $this->dailyAt($time);

        $this->spliceIntoPosition(3, $day);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function dailyAt(string $time): CronContract
    {
        $segments = \explode(':', $time);

        $this->spliceIntoPosition(2, (int) $segments[0])
            ->spliceIntoPosition(1, \count($segments) === 2 ? (int) $segments[1] : 0);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function twiceDaily(int $first = 1, int $second = 13): CronContract
    {
        $hours = $first . ',' . $second;

        $this->spliceIntoPosition(1, 0)
            ->spliceIntoPosition(2, $hours);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function twiceMonthly(int $first = 1, int $second = 16): CronContract
    {
        $days = $first . ',' . $second;

        $this->spliceIntoPosition(1, 0)
            ->spliceIntoPosition(2, 0)
            ->spliceIntoPosition(3, $days);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function weekdays(): CronContract
    {
        $this->spliceIntoPosition(5, '1-5');

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function mondays(): CronContract
    {
        $this->days(1);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function tuesdays(): CronContract
    {
        $this->days(2);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function wednesdays(): CronContract
    {
        $this->days(3);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function thursdays(): CronContract
    {
        $this->days(4);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function fridays(): CronContract
    {
        $this->days(5);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function saturdays(): CronContract
    {
        $this->days(6);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function sundays(): CronContract
    {
        $this->days(0);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function weekly(): CronContract
    {
        $this->spliceIntoPosition(1, 0)
            ->spliceIntoPosition(2, 0)
            ->spliceIntoPosition(5, 0);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function weeklyOn(int $day, string $time = '0:0'): CronContract
    {
        $this->dailyAt($time)
            ->spliceIntoPosition(5, $day);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function between(string $startTime, string $endTime): CronContract
    {
        return $this->when($this->inTimeInterval($startTime, $endTime));
    }

    /**
     * {@inheritdoc}
     */
    public function unlessBetween(string $startTime, string $endTime): CronContract
    {
        return $this->skip($this->inTimeInterval($startTime, $endTime));
    }

    /**
     * {@inheritdoc}
     */
    public function when(Closure $callback): CronContract
    {
        $this->filters[] = $callback;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function skip(Closure $callback): CronContract
    {
        $this->rejects[] = $callback;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function before(Closure $callback): CronContract
    {
        $this->beforeCallbacks[] = $callback;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function after(Closure $callback): CronContract
    {
        $this->afterCallbacks[] = $callback;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getSummaryForDisplay(): string
    {
        if (\is_string($this->description)) {
            return $this->description;
        }

        return $this->buildCommand();
    }

    /**
     * {@inheritdoc}
     */
    public function filtersPass(): bool
    {
        foreach ($this->filters as $callback) {
            if ($this->getInvoker()->call($callback) === false) {
                return false;
            }
        }

        foreach ($this->rejects as $callback) {
            if ($this->getInvoker()->call($callback) === true) {
                return false;
            }
        }

        return true;
    }

    /**
     * Check if os is windows.
     *
     * @return bool
     */
    protected function isWindows(): bool
    {
        return \mb_strtolower(\mb_substr(\PHP_OS, 0, 3)) === 'win';
    }

    /**
     * Finalize the event's command syntax with the correct user.
     *
     * @param string $command
     *
     * @return string
     */
    protected function ensureCorrectUser(string $command): string
    {
        if ($this->user && ! $this->isWindows()) {
            return 'sudo -u ' . $this->user . ' -- sh -c \'' . $command . '\'';
        }

        // http://de2.php.net/manual/en/function.exec.php#56599
        // The "start" command will start a detached process, a similar effect to &. The "/B" option prevents
        // start from opening a new terminal window if the program you are running is a console application.
        if ($this->user && $this->isWindows()) {
            // https://superuser.com/questions/42537/is-there-any-sudo-command-for-windows
            // Options for runas : [{/profile|/noprofile}] [/env] [/netonly] [/smartcard] [/showtrustlevels] [/trustlevel] /user:UserAccountName

            return 'runas ' . $this->user . 'start /B ' . $command;
        }

        if ($this->isWindows()) {
            return 'start /B ' . $command;
        }

        return $command;
    }

    /**
     * Splice the given value into the given position of the expression.
     *
     * @param int        $position
     * @param int|string $value
     *
     * @return \Viserio\Component\Contract\Cron\Cron
     */
    protected function spliceIntoPosition(int $position, $value): CronContract
    {
        $segments                = \explode(' ', $this->expression);
        $segments[$position - 1] = $value;

        return $this->cron(\implode(' ', $segments));
    }

    /**
     * Get the default output depending on the OS.
     *
     * @return string
     */
    protected function getDefaultOutput(): string
    {
        return $this->isWindows() ? 'NUL' : '/dev/null';
    }

    /**
     * Determine if the Cron expression passes.
     *
     * @return bool
     */
    protected function expressionPasses(): bool
    {
        $date = Chronos::now();

        if ($this->timezone !== null) {
            $date->setTimezone($this->timezone);
        }

        return CronExpression::factory($this->expression)->isDue($date->toDateTimeString());
    }

    /**
     * Run the command in the foreground.
     *
     * @return int The exit status code
     */
    protected function runCommandInForeground(): int
    {
        $this->callBeforeCallbacks();

        $process = Process::fromShellCommandline(
            \trim($this->buildCommand(), ' &'),
            $this->path,
            null,
            null,
            null
        );

        $run = $process->run();

        $this->callAfterCallbacks();

        return $run;
    }

    /**
     * Run the command in the background.
     *
     * @return int The exit status code
     */
    protected function runCommandInBackground(): int
    {
        $process = Process::fromShellCommandline(
            $this->buildCommand(),
            $this->path,
            null,
            null,
            null
        );

        return $process->run();
    }

    /**
     * Call all of the "before" callbacks for the cron job.
     *
     * @return void
     */
    protected function callBeforeCallbacks(): void
    {
        foreach ($this->beforeCallbacks as $callback) {
            $this->getInvoker()->call($callback);
        }
    }

    /**
     * Call all of the "after" callbacks for the cron job.
     *
     * @return void
     */
    protected function callAfterCallbacks(): void
    {
        foreach ($this->afterCallbacks as $callback) {
            $this->getInvoker()->call($callback);
        }
    }

    /**
     * Get the mutex name for the scheduled command.
     *
     * @return string
     */
    protected function getMutexName(): string
    {
        return 'schedule-' . \sha1($this->expression . $this->command);
    }

    /**
     * Schedule the cron job to run between start and end time.
     *
     * @param string $startTime
     * @param string $endTime
     *
     * @return \Closure
     */
    protected function inTimeInterval(string $startTime, string $endTime): Closure
    {
        if ($this->isMidnightBetween($startTime, $endTime)) {
            $endTime .= ' +1 day';
        }

        return function () use ($startTime, $endTime) {
            return Chronos::now($this->timezone)->between(
                Chronos::parse($startTime, $this->timezone),
                Chronos::parse($endTime, $this->timezone)
            );
        };
    }

    /**
     * Check if startTime and endTime are before and after midnight.
     *
     * @param string $startTime
     * @param string $endTime
     *
     * @return bool
     */
    private function isMidnightBetween(string $startTime, string $endTime): bool
    {
        return \strtotime($startTime) > \strtotime($endTime);
    }
}


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

use Viserio\Component\Contract\Routing\Pattern;
use Viserio\Component\Contract\Routing\SegmentMatcher as SegmentMatcherContract;
use Viserio\Component\Routing\Matcher\AnyMatcher;
use Viserio\Component\Routing\Matcher\CompoundMatcher;
use Viserio\Component\Routing\Matcher\ExpressionMatcher;
use Viserio\Component\Routing\Matcher\RegexMatcher;
use Viserio\Component\Routing\Matcher\StaticMatcher;

final class MatcherOptimizer
{
    /**
     * Private constructor; non-instantiable.
     *
     * @codeCoverageIgnore
     */
    private function __construct()
    {
    }

    /**
     * Merge same matcher together.
     *
     * @param array $parentMatchers
     * @param array $childMatchers
     *
     * @return array
     */
    public static function mergeMatchers(array $parentMatchers, array $childMatchers): array
    {
        $mergedMatchers = $parentMatchers;

        foreach ($childMatchers as $segment => $childMatcher) {
            if (isset($mergedMatchers[$segment])) {
                $mergedMatchers[$segment] = new CompoundMatcher([$mergedMatchers[$segment], $childMatcher]);
            } else {
                $mergedMatchers[$segment] = $childMatcher;
            }
        }

        return $mergedMatchers;
    }

    /**
     * Optimize matcher and matcher order.
     *
     * @param array $matchers
     *
     * @return array
     */
    public static function optimizeMatchers(array $matchers): array
    {
        foreach ($matchers as $key => $matcher) {
            $matchers[$key] = self::optimizeMatcher($matcher);
        }

        return self::optimizeMatcherOrder($matchers);
    }

    /**
     * Change matcher for a faster one, if available.
     *
     * @param \Viserio\Component\Contract\Routing\SegmentMatcher $matcher
     *
     * @return \Viserio\Component\Contract\Routing\SegmentMatcher
     */
    private static function optimizeMatcher(SegmentMatcherContract $matcher): SegmentMatcherContract
    {
        if ($matcher instanceof RegexMatcher && $matcher->getGroupCount() === 1) {
            $parameterKeys = $matcher->getParameterKeys();

            switch ($matcher->getRegex()) {
                case '/^(' . Pattern::ANY . ')$/':
                    return new AnyMatcher($parameterKeys);
                case '/^(' . Pattern::DIGITS . ')$/':
                    return new ExpressionMatcher('ctype_digit({segment})', $parameterKeys);
                case '/^(' . Pattern::ALPHA . ')$/':
                    return new ExpressionMatcher('ctype_alpha({segment})', $parameterKeys);
                case '/^(' . Pattern::ALPHA_LOWER . ')$/':
                    return new ExpressionMatcher('ctype_lower({segment})', $parameterKeys);
                case '/^(' . Pattern::ALPHA_UPPER . ')$/':
                    return new ExpressionMatcher('ctype_upper({segment})', $parameterKeys);
                case '/^(' . Pattern::ALPHA_NUM . ')$/':
                    return new ExpressionMatcher('ctype_alnum({segment})', $parameterKeys);
                case '/^(' . Pattern::ALPHA_NUM_DASH . ')$/':
                    return new ExpressionMatcher('ctype_alnum(str_replace(\'-\', \'\', {segment}))', $parameterKeys);

                default:
                    return $matcher;
            }
        }

        return $matcher;
    }

    /**
     * Optimizing the matcher order, unknown types are added last.
     *
     * @param array $matchers
     *
     * @return array
     */
    private static function optimizeMatcherOrder(array $matchers): array
    {
        $computationalCostOrder = [
            AnyMatcher::class,
            StaticMatcher::class,
            ExpressionMatcher::class,
            RegexMatcher::class,
            // Unknown types last
            SegmentMatcherContract::class,
        ];

        $groups = [];

        foreach ($computationalCostOrder as $type) {
            foreach ($matchers as $index => $matcher) {
                if ($matcher instanceof $type) {
                    unset($matchers[$index]);
                    $groups[$index] = $matcher;
                }
            }
        }

        return $groups;
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Log\LoggerInterface;
use Viserio\Component\Contract\Exception\HttpHandler as HttpHandlerContract;
use Viserio\Component\Contract\View\Factory as FactoryContract;
use Viserio\Component\Exception\Displayer\HtmlDisplayer;
use Viserio\Component\Exception\Displayer\JsonApiDisplayer;
use Viserio\Component\Exception\Displayer\JsonDisplayer;
use Viserio\Component\Exception\Displayer\SymfonyDisplayer;
use Viserio\Component\Exception\Displayer\ViewDisplayer;
use Viserio\Component\Exception\Displayer\WhoopsJsonDisplayer;
use Viserio\Component\Exception\Displayer\WhoopsPrettyDisplayer;
use Viserio\Component\Exception\Filter\CanDisplayFilter;
use Viserio\Component\Exception\Filter\ContentTypeFilter;
use Viserio\Component\Exception\Filter\VerboseFilter;
use Viserio\Component\Exception\Http\Handler;
use Whoops\Run;

class HttpExceptionServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [
            HtmlDisplayer::class         => [self::class, 'createHtmlDisplayer'],
            JsonDisplayer::class         => [self::class, 'createJsonDisplayer'],
            JsonApiDisplayer::class      => [self::class, 'createJsonApiDisplayer'],
            SymfonyDisplayer::class      => [self::class, 'createSymfonyDisplayer'],
            ViewDisplayer::class         => [self::class, 'createViewDisplayer'],
            WhoopsPrettyDisplayer::class => [self::class, 'createWhoopsPrettyDisplayer'],
            WhoopsJsonDisplayer::class   => [self::class, 'createWhoopsJsonDisplayer'],
            VerboseFilter::class         => [self::class, 'createVerboseFilter'],
            ContentTypeFilter::class     => [self::class, 'createContentTypeFilter'],
            CanDisplayFilter::class      => [self::class, 'createCanDisplayFilter'],
            HttpHandlerContract::class   => [self::class, 'createExceptionHandler'],
            Handler::class               => static function (ContainerInterface $container) {
                return $container->get(HttpHandlerContract::class);
            },
        ];
    }

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

    /**
     * Create a new Handler instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Contract\Exception\HttpHandler
     */
    public static function createExceptionHandler(ContainerInterface $container): HttpHandlerContract
    {
        $logger = null;

        if ($container->has(LoggerInterface::class)) {
            $logger = $container->get(LoggerInterface::class);
        }

        $config = $container->get('config');

        $handler = new Handler(
            $config,
            $container->get(ResponseFactoryInterface::class),
            $logger
        );

        $handler->addFilter($container->get(VerboseFilter::class), 32);
        $handler->addFilter($container->get(CanDisplayFilter::class), 64);
        $handler->addFilter($container->get(ContentTypeFilter::class), 128);

        $handler->addDisplayer($container->get(SymfonyDisplayer::class), 64);

        if ($container->has(FactoryContract::class)) {
            $handler->addDisplayer($container->get(ViewDisplayer::class), 128);
        }

        $handler->addDisplayer($container->get(HtmlDisplayer::class), 256);

        if (\class_exists(Run::class)) {
            $handler->addDisplayer($container->get(WhoopsPrettyDisplayer::class), 32);
            $handler->addDisplayer($container->get(WhoopsJsonDisplayer::class), 512);
        }

        $handler->addDisplayer($container->get(JsonDisplayer::class), 1024);
        $handler->addDisplayer($container->get(JsonApiDisplayer::class), 2048);

        return $handler;
    }

    /**
     * Create a new HtmlDisplayer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Displayer\HtmlDisplayer
     */
    public static function createHtmlDisplayer(ContainerInterface $container): HtmlDisplayer
    {
        return new HtmlDisplayer(
            $container->get(ResponseFactoryInterface::class),
            $container->get('config')
        );
    }

    /**
     * Create a new SymfonyDisplayer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Displayer\SymfonyDisplayer
     */
    public static function createSymfonyDisplayer(ContainerInterface $container): SymfonyDisplayer
    {
        return new SymfonyDisplayer($container->get(ResponseFactoryInterface::class));
    }

    /**
     * Create a new JsonDisplayer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Displayer\JsonDisplayer
     */
    public static function createJsonDisplayer(ContainerInterface $container): JsonDisplayer
    {
        return new JsonDisplayer($container->get(ResponseFactoryInterface::class));
    }

    /**
     * Create a new JsonApiDisplayer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Displayer\JsonApiDisplayer
     */
    public static function createJsonApiDisplayer(ContainerInterface $container): JsonApiDisplayer
    {
        return new JsonApiDisplayer($container->get(ResponseFactoryInterface::class));
    }

    /**
     * Create a new ViewDisplayer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Displayer\ViewDisplayer
     */
    public static function createViewDisplayer(ContainerInterface $container): ViewDisplayer
    {
        return new ViewDisplayer(
            $container->get(ResponseFactoryInterface::class),
            $container->get(FactoryContract::class)
        );
    }

    /**
     * Create a new WhoopsPrettyDisplayer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Displayer\WhoopsPrettyDisplayer
     */
    public static function createWhoopsPrettyDisplayer(ContainerInterface $container): WhoopsPrettyDisplayer
    {
        return new WhoopsPrettyDisplayer(
            $container->get(ResponseFactoryInterface::class),
            $container->get('config')
        );
    }

    /**
     * Create a new WhoopsJsonDisplayer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Displayer\WhoopsJsonDisplayer
     */
    public static function createWhoopsJsonDisplayer(ContainerInterface $container): WhoopsJsonDisplayer
    {
        return new WhoopsJsonDisplayer($container->get(ResponseFactoryInterface::class));
    }

    /**
     * Create a new VerboseFilter instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Exception\Filter\VerboseFilter
     */
    public static function createVerboseFilter(ContainerInterface $container): VerboseFilter
    {
        return new VerboseFilter($container->get('config'));
    }

    /**
     * Create a new ContentTypeFilter instance.
     *
     * @return \Viserio\Component\Exception\Filter\ContentTypeFilter
     */
    public static function createContentTypeFilter(): ContentTypeFilter
    {
        return new ContentTypeFilter();
    }

    /**
     * Create a new CanDisplayFilter instance.
     *
     * @return \Viserio\Component\Exception\Filter\CanDisplayFilter
     */
    public static function createCanDisplayFilter(): CanDisplayFilter
    {
        return new CanDisplayFilter();
    }
}


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

use Narrowspark\Http\Message\Util\InteractsWithContentTypes;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;
use Viserio\Component\Contract\Exception\Filter as FilterContract;

class ContentTypeFilter implements FilterContract
{
    /**
     * {@inheritdoc}
     */
    public function filter(
        array $displayers,
        ServerRequestInterface $request,
        Throwable $original,
        Throwable $transformed,
        int $code
    ): array {
        foreach ($displayers as $index => $displayer) {
            if (! InteractsWithContentTypes::accepts([$displayer->getContentType()], $request)) {
                unset($displayers[$index]);
            }
        }

        return \array_values($displayers);
    }
}


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

use Swift_Events_EventListener;
use Swift_Events_SendEvent;
use Swift_Mime_SimpleMessage;
use Swift_Transport;

abstract class AbstractTransport implements Swift_Transport
{
    /**
     * The plug-ins registered with the transport.
     *
     * @var array
     */
    protected $plugins = [];

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

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function start(): void
    {
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function stop(): void
    {
    }

    /**
     * Register a plug-in with the transport.
     *
     * @param \Swift_Events_EventListener $plugin
     *
     * @return void
     */
    public function registerPlugin(Swift_Events_EventListener $plugin): void
    {
        $this->plugins[] = $plugin;
    }

    /**
     * Iterate through registered plugins and execute plugins' methods.
     *
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return void
     */
    protected function beforeSendPerformed(Swift_Mime_SimpleMessage $message): void
    {
        $event = new Swift_Events_SendEvent($this, $message);

        foreach ($this->plugins as $plugin) {
            if (\method_exists($plugin, 'beforeSendPerformed')) {
                $plugin->beforeSendPerformed($event);
            }
        }
    }

    /**
     * Get the number of recipients.
     *
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return int
     */
    protected function numberOfRecipients(Swift_Mime_SimpleMessage $message): int
    {
        $to  = $message->getTo()  ?? [];
        $cc  = $message->getCc()  ?? [];
        $bcc = $message->getBcc() ?? [];

        return \count(\array_merge($to, $cc, $bcc));
    }

    /**
     * Iterate through registered plugins and execute plugins' methods.
     *
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return void
     */
    protected function sendPerformed(Swift_Mime_SimpleMessage $message): void
    {
        $event = new Swift_Events_SendEvent($this, $message);

        foreach ($this->plugins as $plugin) {
            if (\method_exists($plugin, 'sendPerformed')) {
                $plugin->sendPerformed($event);
            }
        }
    }
}


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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;
use Viserio\Component\Contract\Events\EventManager as EventManagerContract;
use Viserio\Component\Contract\Exception\HttpHandler as HttpHandlerContract;
use Viserio\Component\Contract\Foundation\BootstrapState as BootstrapStateContract;
use Viserio\Component\Contract\Foundation\HttpKernel as HttpKernelContract;
use Viserio\Component\Contract\Foundation\Terminable as TerminableContract;
use Viserio\Component\Contract\OptionsResolver\RequiresValidatedConfig as RequiresValidatedConfigContract;
use Viserio\Component\Contract\Routing\Dispatcher as DispatcherContract;
use Viserio\Component\Contract\Routing\Router as RouterContract;
use Viserio\Component\Foundation\AbstractKernel;
use Viserio\Component\Foundation\BootstrapManager;
use Viserio\Component\HttpFoundation\Event\KernelExceptionEvent;
use Viserio\Component\HttpFoundation\Event\KernelFinishRequestEvent;
use Viserio\Component\HttpFoundation\Event\KernelRequestEvent;
use Viserio\Component\HttpFoundation\Event\KernelTerminateEvent;
use Viserio\Component\Pipeline\Pipeline;
use Viserio\Component\Routing\Dispatcher\MiddlewareBasedDispatcher;
use Viserio\Component\Routing\Pipeline as RoutingPipeline;
use Viserio\Component\StaticalProxy\StaticalProxy;

class Kernel extends AbstractKernel implements HttpKernelContract, TerminableContract, RequiresValidatedConfigContract
{
    /**
     * List of allowed bootstrap types.
     *
     * @internal
     *
     * @var array
     */
    protected static $allowedBootstrapTypes = ['global', 'http'];

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        $options = [
            'name'                => 'Narrowspark',
            'skip_middleware'     => false,
            'middleware'          => [],
            'route_middleware'    => [],
            'middleware_groups'   => [],
            'middleware_priority' => [],
        ];

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

    /**
     * {@inheritdoc}
     */
    public static function getOptionValidators(): array
    {
        return [
            'middleware'          => ['array'],
            'route_middleware'    => ['array'],
            'middleware_groups'   => ['array'],
            'middleware_priority' => ['array'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function handle(ServerRequestInterface $serverRequest): ResponseInterface
    {
        $serverRequest = $serverRequest->withAddedHeader('X-Php-Ob-Level', (string) \ob_get_level());

        $this->bootstrap();

        $container = $this->getContainer();
        $events    = null;

        if ($container->has(EventManagerContract::class)) {
            $events = $container->get(EventManagerContract::class);
            $events->trigger(new KernelRequestEvent($this, $serverRequest));
        }

        // Passes the request to the container
        $container->instance(ServerRequestInterface::class, $serverRequest);

        if (\class_exists(StaticalProxy::class)) {
            StaticalProxy::clearResolvedInstance(ServerRequestInterface::class);
        }

        $response = $this->handleRequest($serverRequest, $events);

        // Stop PHP sending a Content-Type automatically.
        \ini_set('default_mimetype', '');

        return $response;
    }

    /**
     * {@inheritdoc}
     */
    public function terminate(ServerRequestInterface $serverRequest, ResponseInterface $response): void
    {
        $container = $this->getContainer();

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

        if ($container->has(EventManagerContract::class)) {
            $container->get(EventManagerContract::class)
                ->trigger(new KernelTerminateEvent($this, $serverRequest, $response));
        }
    }

    /**
     * Bootstrap the kernel for HTTP requests.
     *
     * @return void
     */
    public function bootstrap(): void
    {
        $container        = $this->getContainer();
        $bootstrapManager = $container->get(BootstrapManager::class);

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

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

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

            $bootstrapManager->bootstrapWith($bootstraps);

            if ($this->isDebug() && ! isset($_ENV['SHELL_VERBOSITY']) && ! isset($_SERVER['SHELL_VERBOSITY'])) {
                \putenv('SHELL_VERBOSITY=3');

                $_ENV['SHELL_VERBOSITY']    = 3;
                $_SERVER['SHELL_VERBOSITY'] = 3;
            }

            $dispatcher = $container->get(DispatcherContract::class);

            if ($dispatcher instanceof MiddlewareBasedDispatcher) {
                $dispatcher->setMiddlewarePriorities($this->resolvedOptions['middleware_priority']);
                $dispatcher->withMiddleware($this->resolvedOptions['route_middleware']);

                foreach ($this->resolvedOptions['middleware_groups'] as $key => $middleware) {
                    $dispatcher->setMiddlewareGroup($key, $middleware);
                }
            }
        }
    }

    /**
     * Convert request into response.
     *
     * @param \Psr\Http\Message\ServerRequestInterface             $serverRequest
     * @param null|\Viserio\Component\Contract\Events\EventManager $events
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function handleRequest(
        ServerRequestInterface $serverRequest,
        ?EventManagerContract $events
    ): ResponseInterface {
        try {
            if ($events !== null) {
                $events->trigger(new KernelFinishRequestEvent($this, $serverRequest));
            }

            $response = $this->sendRequestThroughRouter($serverRequest);
        } catch (Throwable $exception) {
            $this->reportException($exception);

            $response = $this->renderException($serverRequest, $exception);

            if ($events !== null) {
                $events->trigger(new KernelExceptionEvent($this, $serverRequest, $response));
            }
        }

        return $response;
    }

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

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

    /**
     * Render the exception to a response.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request
     * @param \Throwable                               $exception
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function renderException(ServerRequestInterface $request, Throwable $exception): ResponseInterface
    {
        $container = $this->getContainer();

        if ($container->has(HttpHandlerContract::class)) {
            return $container->get(HttpHandlerContract::class)->render($request, $exception);
        }

        throw $exception;
    }

    /**
     * Send the given request through the middleware / router.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function sendRequestThroughRouter(ServerRequestInterface $request): ResponseInterface
    {
        $container  = $this->getContainer();
        $router     = $container->get(RouterContract::class);
        $dispatcher = $container->get(DispatcherContract::class);

        $dispatcher->setCachePath($this->getStoragePath('framework' . \DIRECTORY_SEPARATOR . 'routes.cache.php'));
        $dispatcher->refreshCache($this->getEnvironment() !== 'prod');

        if (\class_exists(Pipeline::class)) {
            return $this->pipeRequestThroughMiddlewareAndRouter($request, $router);
        }

        $container->instance(ServerRequestInterface::class, $request);

        return $router->dispatch($request);
    }

    /**
     * Pipes the request through given middleware and dispatch a response.
     *
     * @param \Psr\Http\Message\ServerRequestInterface   $request
     * @param \Viserio\Component\Contract\Routing\Router $router
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function pipeRequestThroughMiddlewareAndRouter(
        ServerRequestInterface $request,
        RouterContract $router
    ): ResponseInterface {
        $container = $this->getContainer();

        return (new RoutingPipeline())
            ->setContainer($container)
            ->send($request)
            ->through($this->resolvedOptions['skip_middleware'] ? [] : $this->resolvedOptions['middleware'])
            ->then(static function ($request) use ($router, $container) {
                $container->instance(ServerRequestInterface::class, $request);

                return $router->dispatch($request);
            });
    }

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

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

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

        $container->alias(HttpKernelContract::class, self::class);
        $container->alias(HttpKernelContract::class, 'http_kernel');
    }
}


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

use ReflectionClass;
use Symfony\Component\VarExporter\VarExporter;
use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;

class OptionReaderCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'option:read';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'option:read
        [class : Name of the class to reflect.]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Reads the provided configuration file and displays options for the provided class name.';

    /**
     * {@inheritdoc}
     */
    public function handle(): int
    {
        $className       = $this->argument('class');
        $reflectionClass = new ReflectionClass($className);

        $configs = (new OptionsReader())->readConfig($reflectionClass);

        if (\count($configs) !== 0) {
            $interfaces = \array_flip($reflectionClass->getInterfaceNames());

            if (isset($interfaces[RequiresComponentConfigContract::class])) {
                $dimensions = $className::getDimensions();
                $configs    = $configs[\end($dimensions)];
            }
        }

        $this->info('Output array:' . \PHP_EOL . \PHP_EOL . VarExporter::export($configs));

        return 0;
    }
}


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

use Symfony\Component\Process\PhpExecutableFinder;
use Viserio\Component\Console\Application;
use Viserio\Component\Contract\Cache\Traits\CacheItemPoolAwareTrait;
use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Cron\Cron as CronContract;
use Viserio\Component\Contract\Cron\Exception\LogicException;
use Viserio\Component\Contract\Cron\Schedule as ScheduleContract;

class Schedule implements ScheduleContract
{
    use ContainerAwareTrait;
    use CacheItemPoolAwareTrait;

    /**
     * All of the cron jobs on the schedule.
     *
     * @var array
     */
    private $jobs = [];

    /**
     * Console path or console name that should be called.
     *
     * @var null|string
     */
    private $console;

    /**
     * Path for the working directory.
     *
     * @var string
     */
    private $workingDirPath;

    /**
     * Create a new Schedule instance.
     *
     * @param string      $path        path for the working directory
     * @param null|string $consoleName
     */
    public function __construct(string $path, ?string $consoleName = null)
    {
        $this->workingDirPath = $path;
        $this->console        = $consoleName;
    }

    /**
     * {@inheritdoc}
     */
    public function call($callback, array $parameters = []): CallbackCron
    {
        $cron = new CallbackCron($callback, $parameters);

        if ($this->cachePool !== null) {
            $cron->setCacheItemPool($this->getCacheItemPool());
        }

        $cron->setPath($this->workingDirPath);

        if ($this->container !== null) {
            $cron->setContainer($this->container);
        }

        $this->jobs[] = $cron;

        return $cron;
    }

    /**
     * {@inheritdoc}
     */
    public function command(string $command, array $parameters = []): CronContract
    {
        if ($this->container !== null && $this->container->has($command)) {
            $command = $this->container->get($command)->getName();
        }

        if (\defined('CEREBRO_BINARY')) {
            return $this->exec(Application::formatCommandString($command), $parameters);
        }

        if ($this->console !== null) {
            $binary  = \escapeshellarg((string) (new PhpExecutableFinder())->find(false));
            $console = \escapeshellarg($this->console);

            return $this->exec(\sprintf('%s %s %s', $binary, $console, $command), $parameters);
        }

        throw new LogicException('You need to set a console name or a path to a console, before you call command.');
    }

    /**
     * {@inheritdoc}
     */
    public function exec(string $command, array $parameters = []): CronContract
    {
        if (\count($parameters) !== 0) {
            $command .= ' ' . $this->compileParameters($parameters);
        }

        $cron = new Cron($command);

        if ($this->cachePool !== null) {
            $cron->setCacheItemPool($this->getCacheItemPool());
        }

        $cron->setPath($this->workingDirPath);

        if ($this->container !== null) {
            $cron->setContainer($this->container);
        }

        $this->jobs[] = $cron;

        return $cron;
    }

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

    /**
     * {@inheritdoc}
     */
    public function dueCronJobs(string $environment, bool $isMaintenance = false): array
    {
        return \array_filter($this->jobs, static function (CronContract $job) use ($environment, $isMaintenance) {
            return $job->isDue($environment, $isMaintenance);
        });
    }

    /**
     * Compile parameters for a command.
     *
     * @param array $parameters
     *
     * @return string
     */
    protected function compileParameters(array $parameters): string
    {
        $keys = \array_keys($parameters);

        $items = \array_map(static function ($value, $key) {
            if (\is_array($value)) {
                $value = \array_map('escapeshellarg', $value);

                $value = \implode(' ', $value);
            } elseif (! \is_numeric($value) && ! \preg_match('/^(-.$|--.*)/', $value)) {
                $value = \escapeshellarg($value);
            }

            return \is_numeric($key) ? $value : \sprintf('%s=%s', $key, $value);
        }, $parameters, $keys);

        return \implode(' ', \array_combine($keys, $items));
    }
}


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

use InvalidArgumentException;
use Viserio\Component\Contract\Parser\Exception\ParseException;
use Viserio\Component\Contract\Parser\Parser as ParserContract;
use Viserio\Component\Parser\Utils\XmlUtils;

/**
 * For more infos.
 *
 * @see http://doc.qt.io/qt-5/linguist-ts-file-Parser.html
 * @see http://svn.ez.no/svn/ezcomponents/trunk/Translation/docs/linguist-Parser.txt
 */
class QtParser implements ParserContract
{
    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        try {
            $dom = XmlUtils::loadString($payload);
        } catch (InvalidArgumentException $exception) {
            throw new ParseException([
                'message' => $exception->getMessage(),
                'code'    => $exception->getCode(),
                'file'    => $exception->getFile(),
                'line'    => $exception->getLine(),
            ]);
        }

        $internalErrors = \libxml_use_internal_errors(true);

        \libxml_clear_errors();

        $xml   = XmlUtils::importDom($dom);
        $datas = [];

        foreach ((array) $xml->xpath('//TS/context') as $node) {
            $name         = (string) $node->name;
            $datas[$name] = [];

            foreach ($node->message as $message) {
                $translation           = $message->translation;
                $translationAttributes = (array) $translation->attributes();
                $attributes            = \reset($translationAttributes);

                $datas[$name][] = [
                    'source'      => (string) $message->source,
                    'translation' => [
                        'content'    => (string) $translation,
                        'attributes' => $attributes,
                    ],
                ];
            }
        }

        \libxml_use_internal_errors($internalErrors);

        return $datas;
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Contract\Profiler\Profiler as ProfilerContract;
use Viserio\Component\Contract\Translation\Translator as TranslatorContract;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;
use Viserio\Component\Translation\DataCollector\ViserioTranslationDataCollector;

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

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

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

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

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

    /**
     * Extend viserio profiler with data collector.
     *
     * @param \Psr\Container\ContainerInterface                  $container
     * @param null|\Viserio\Component\Contract\Profiler\Profiler $profiler
     *
     * @return null|\Viserio\Component\Contract\Profiler\Profiler
     */
    public static function extendProfiler(
        ContainerInterface $container,
        ?ProfilerContract $profiler = null
    ): ?ProfilerContract {
        if ($profiler !== null) {
            $options = self::resolveOptions($container->get('config'));

            if ($options['collector']['translation']) {
                $profiler->addCollector(new ViserioTranslationDataCollector(
                    $container->get(TranslatorContract::class)
                ));
            }
        }

        return $profiler;
    }
}


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

use Closure;
use Interop\Container\ServiceProviderInterface;
use Invoker\Invoker;
use Invoker\InvokerInterface;
use Invoker\ParameterResolver\AssociativeArrayResolver;
use Invoker\ParameterResolver\Container\ParameterNameContainerResolver;
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use Invoker\ParameterResolver\DefaultValueResolver;
use Invoker\ParameterResolver\NumericArrayResolver;
use Invoker\ParameterResolver\ResolverChain;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use Viserio\Component\Contract\Container\Container as ContainerContract;
use Viserio\Component\Contract\Container\ContextualBindingBuilder as ContextualBindingBuilderContract;
use Viserio\Component\Contract\Container\Exception\ContainerException;
use Viserio\Component\Contract\Container\Exception\NotFoundException;
use Viserio\Component\Contract\Container\Exception\UnresolvableDependencyException;
use Viserio\Component\Contract\Container\Factory as FactoryContract;
use Viserio\Component\Contract\Container\Types as TypesContract;

class Container extends ContainerResolver implements ContainerContract, InvokerInterface, ContextualBindingBuilderContract
{
    /**
     * The container's bindings.
     *
     * @var array
     */
    protected $bindings = [];

    /**
     * The container's extenders.
     *
     * @var array
     */
    protected $extenders = [];

    /**
     * Array full of container implementing the ContainerInterface.
     *
     * @var \Psr\Container\ContainerInterface[]
     */
    protected $delegates = [];

    /**
     * Array containing immutable instances.
     *
     * @var array
     */
    protected $immutable = [];

    /**
     * The normalized abstract instance.
     *
     * @var string
     */
    protected $abstract;

    /**
     * The concrete instance.
     *
     * @var mixed
     */
    protected $concrete;

    /**
     * Abstract target.
     *
     * @var string
     */
    protected $parameter;

    /**
     * Contextual parameters.
     *
     * @var array
     */
    protected $contextualParameters = [];

    /**
     * Invoker instance.
     *
     * @var null|\Invoker\InvokerInterface
     */
    private $invoker;

    /**
     * Create a new container instance.
     */
    public function __construct()
    {
        // Auto-register the container
        $this->instance(Container::class, $this);
        $this->instance(ContainerContract::class, $this);
        $this->instance(ContainerInterface::class, $this);
        $this->instance(FactoryContract::class, $this);
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function getBindings(): array
    {
        return $this->bindings;
    }

    /**
     * {@inheritdoc}
     */
    public function bind($abstract, $concrete = null): void
    {
        $concrete = $concrete ?? $abstract;

        if (\is_array($abstract)) {
            $this->bindService(\key($abstract), $concrete);
            $this->alias(\key($abstract), \current($abstract));
        } else {
            $this->bindService($abstract, $concrete);
        }
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function bindIf(string $abstract, $concrete = null): void
    {
        if (! $this->has($abstract)) {
            $this->bind($abstract, $concrete);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function instance(string $abstract, $instance): void
    {
        $this->bindPlain($abstract, $instance);
    }

    /**
     * {@inheritdoc}
     */
    public function singleton(string $abstract, $concrete = null): void
    {
        $concrete = $concrete ?? $abstract;

        $this->bindSingleton($abstract, $concrete);
    }

    /**
     * {@inheritdoc}
     */
    public function alias(string $abstract, string $alias): void
    {
        $this->bindings[$alias] = &$this->bindings[$abstract];
    }

    /**
     * {@inheritdoc}
     */
    public function extend(string $abstract, Closure $closure): void
    {
        $this->extenders[$abstract][] = $closure;
    }

    /**
     * {@inheritdoc}
     */
    public function delegate(ContainerInterface $container): ContainerContract
    {
        $this->delegates[] = $container;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function hasInDelegate(string $abstract): bool
    {
        foreach ($this->delegates as $container) {
            if ($container->has($abstract)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function forget(string $abstract): void
    {
        unset($this->bindings[$abstract]);
    }

    /**
     * {@inheritdoc}
     */
    public function resolve($subject, array $parameters = [])
    {
        if (\is_string($subject) && isset($this->contextualParameters[$subject])) {
            $contextualParameters = $this->contextualParameters[$subject];

            foreach ($contextualParameters as $key => $value) {
                if ($value instanceof Closure) {
                    $contextualParameters[$key] = $value($this);
                }
            }

            $parameters = \array_replace($contextualParameters, $parameters);
        }

        if ($this->has($subject)) {
            return $this->resolveBound($subject, $parameters);
        }

        return $this->resolveNonBound($subject, $parameters);
    }

    /**
     * {@inheritdoc}
     */
    public function resolveBound(string $abstract, array $parameters = [])
    {
        $binding     = $this->bindings[$abstract];
        $concrete    = $binding[TypesContract::VALUE];
        $bindingType = $binding[TypesContract::BINDING_TYPE];

        if ($this->isComputed($binding)) {
            return $binding[TypesContract::VALUE];
        }

        if ($concrete instanceof Closure) {
            \array_unshift($parameters, $this);
        }

        if ($bindingType === TypesContract::PLAIN) {
            $resolved = $this->resolvePlain($abstract);
        } elseif ($bindingType === TypesContract::SERVICE) {
            $resolved = $this->resolveService($abstract, $parameters);
        } else {
            $resolved = $this->resolveSingleton($abstract, $parameters);
        }

        if (\is_string($abstract)) {
            $this->extendResolved($abstract, $resolved);
        }

        return $resolved;
    }

    /**
     * {@inheritdoc}
     */
    public function resolveNonBound($abstract, array $parameters = [])
    {
        if ($abstract instanceof Closure) {
            \array_unshift($parameters, $this);
        }

        if (\is_string($abstract) && \mb_strpos($abstract, '::')) {
            $parts    = \explode('::', $abstract, 2);
            $abstract = [$this->resolve($parts[0]), $parts[1]];
        }

        $resolved = parent::resolve($abstract, $parameters);

        if (\is_string($abstract)) {
            $this->extendResolved($abstract, $resolved);
        }

        return $resolved;
    }

    /**
     * {@inheritdoc}
     */
    public function when(string $concrete): ContextualBindingBuilderContract
    {
        $this->abstract = $concrete;

        if (isset($this->bindings[$this->abstract])) {
            $this->concrete = $this->bindings[$this->abstract][TypesContract::VALUE];
        } elseif (\mb_strpos($this->abstract, '::')) {
            $this->concrete = \explode('::', $this->abstract, 2);
        } else {
            $this->concrete = $this->abstract;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function needs(string $abstract): ContextualBindingBuilderContract
    {
        $this->parameter = $abstract;

        if ($this->parameter[0] === '$') {
            $this->parameter = \mb_substr($this->parameter, 1);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function give($implementation)
    {
        if (! ($reflector = $this->getReflector($this->concrete))) {
            throw new UnresolvableDependencyException(\sprintf('[%s] is not resolvable.', $this->concrete));
        }

        if ($reflector instanceof ReflectionClass && ! ($reflector = $reflector->getConstructor())) {
            throw new UnresolvableDependencyException(\sprintf('[%s] must have a constructor.', $this->concrete));
        }

        $reflectionParameters = $reflector->getParameters();
        $contextualParameters = &$this->contextualParameters[$this->abstract];

        foreach ($reflectionParameters as $key => $parameter) {
            $class = $parameter->getClass();

            if ($this->parameter === $parameter->name) {
                return $contextualParameters[$key] = $implementation;
            }

            if ($class && $this->parameter === $class->name) {
                return $contextualParameters[$key] = $this->contextualBindingFormat($implementation, $class);
            }
        }

        $concrete = \gettype($this->concrete);

        if (\is_object($this->concrete)) {
            $concrete = \get_class($this->concrete);
        } elseif (\is_array($this->concrete)) {
            $concrete = $this->concrete[0];
        } elseif (\is_string($this->concrete)) {
            $concrete = $this->concrete;
        }

        throw new UnresolvableDependencyException(\sprintf(
            'Parameter [%s] cannot be injected in [%s].',
            $this->parameter,
            $concrete
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function get($id)
    {
        if (! \is_string($id)) {
            throw new ContainerException(\sprintf(
                'The id parameter must be of type string, [%s] given.',
                \is_object($id) ? \get_class($id) : \gettype($id)
            ));
        }

        if (isset($this->bindings[$id])) {
            return $this->resolve($id);
        }

        $resolved = $this->getFromDelegate($id);

        if ((bool) $resolved) {
            return $resolved;
        }

        throw new NotFoundException(
            \sprintf('Abstract [%s] is not being managed by the container.', $id)
        );
    }

    /**
     * {@inheritdoc}
     */
    public function has($id)
    {
        if (! \is_string($id)) {
            throw new ContainerException(\sprintf(
                'The name parameter must be of type string, [%s] given.',
                \is_object($id) ? \get_class($id) : \gettype($id)
            ));
        }

        $abstract = $id;

        if (isset($this->bindings[$abstract])) {
            return true;
        }

        return $this->hasInDelegate($id);
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function call($callable, array $parameters = [])
    {
        return $this->getInvoker()->call($callable, $parameters);
    }

    /**
     * {@inheritdoc}
     */
    public function register(ServiceProviderInterface $provider, array $parameters = []): ContainerContract
    {
        foreach ($provider->getFactories() as $key => $callable) {
            $this->singleton($key, static function ($container) use ($callable) {
                return $callable($container, null);
            });
        }

        foreach ($provider->getExtensions() as $key => $callable) {
            if ($this->has($key)) {
                $this->extend($key, static function ($previous, $container) use ($callable) {
                    // Extend a previous entry
                    return $callable($container, $previous);
                });
            } else {
                $this->singleton($key, static function ($container) use ($callable) {
                    return $callable($container, null);
                });
            }
        }

        foreach ($parameters as $key => $value) {
            $this->instance($key, $value);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function isComputed($binding): bool
    {
        return $binding[TypesContract::IS_RESOLVED] && $binding[TypesContract::BINDING_TYPE] !== TypesContract::SERVICE;
    }

    /**
     * Set the value at a given offset.
     *
     * @param string $offset
     * @param mixed  $value
     */
    public function offsetSet($offset, $value): void
    {
        if (\is_string($value)) {
            $this->bindPlain($offset, $value);
        } else {
            $this->bindService($offset, $value);
        }
    }

    /**
     * Get the value at a given offset.
     *
     * @param string $offset
     *
     * @return mixed
     */
    public function offsetGet($offset)
    {
        return $this->get($offset);
    }

    /**
     * Unset the value at a given offset.
     *
     * @param string $offset
     */
    public function offsetUnset($offset): void
    {
        unset($this->bindings[$offset]);
    }

    /**
     * Determine if a given offset exists.
     *
     * @param string $offset
     *
     * @return bool
     */
    public function offsetExists($offset): bool
    {
        return $this->has($offset);
    }

    /**
     * Get a configured instance of invoker.
     *
     * @return \Invoker\InvokerInterface
     *
     * @codeCoverageIgnore
     */
    protected function getInvoker(): InvokerInterface
    {
        if (! $this->invoker) {
            $parameterResolver = [
                new NumericArrayResolver(),
                new AssociativeArrayResolver(),
                new DefaultValueResolver(),
                new TypeHintContainerResolver($this),
                new ParameterNameContainerResolver($this),
            ];

            $this->invoker = new Invoker(new ResolverChain($parameterResolver), $this);
        }

        return $this->invoker;
    }

    /**
     * Attempt to get a service from the stack of delegated backup containers.
     *
     * @param string $abstract
     *
     * @return mixed
     */
    protected function getFromDelegate(string $abstract)
    {
        foreach ($this->delegates as $container) {
            if ($container->has($abstract)) {
                return $container->get($abstract);
            }
            // @codeCoverageIgnoreStart
            continue;
            // @codeCoverageIgnoreEnd
        }

        return false;
    }

    /**
     * Bind a plain value.
     *
     * @param string $abstract
     * @param mixed  $concrete
     */
    protected function bindPlain(string $abstract, $concrete): void
    {
        $this->bindings[$abstract] = [
            TypesContract::VALUE        => $concrete,
            TypesContract::IS_RESOLVED  => false,
            TypesContract::BINDING_TYPE => TypesContract::PLAIN,
        ];
    }

    /**
     * Bind a value which need to be resolved each time.
     *
     * @param string $abstract
     * @param mixed  $concrete
     */
    protected function bindService(string $abstract, $concrete): void
    {
        $this->bindings[$abstract] = [
            TypesContract::VALUE        => $concrete,
            TypesContract::IS_RESOLVED  => false,
            TypesContract::BINDING_TYPE => TypesContract::SERVICE,
        ];
    }

    /**
     * Bind a value which need to be resolved one time.
     *
     * @param string $abstract
     * @param mixed  $concrete
     */
    protected function bindSingleton(string $abstract, $concrete): void
    {
        $this->bindings[$abstract] = [
            TypesContract::VALUE        => $concrete,
            TypesContract::IS_RESOLVED  => false,
            TypesContract::BINDING_TYPE => TypesContract::SINGLETON,
        ];
    }

    /**
     * Resolve a plain value from the container.
     *
     * @param string $abstract
     *
     * @return mixed
     */
    protected function resolvePlain(string $abstract)
    {
        $binding                             = &$this->bindings[$abstract];
        $binding[TypesContract::IS_RESOLVED] = true;

        return $binding[TypesContract::VALUE];
    }

    /**
     * Resolve a service from the container.
     *
     * @param string $abstract
     * @param array  $parameters
     *
     * @return mixed
     */
    protected function resolveService(string $abstract, array $parameters = [])
    {
        $binding                             = &$this->bindings[$abstract];
        $binding[TypesContract::IS_RESOLVED] = true;

        return parent::resolve($binding[TypesContract::VALUE], $parameters);
    }

    /**
     * Resolve a singleton from the container.
     *
     * @param string $abstract
     * @param array  $parameters
     *
     * @return mixed
     */
    protected function resolveSingleton(string $abstract, array $parameters = [])
    {
        $binding = &$this->bindings[$abstract];

        $binding[TypesContract::VALUE]       = parent::resolve($binding[TypesContract::VALUE], $parameters);
        $binding[TypesContract::IS_RESOLVED] = true;

        return $binding[TypesContract::VALUE];
    }

    /**
     * Extend a resolved subject.
     *
     * @param string $abstract
     * @param mixed  $resolved
     *
     * @return void
     */
    protected function extendResolved($abstract, &$resolved): void
    {
        if (! isset($this->extenders[$abstract])) {
            return;
        }

        $binding = $this->bindings[$abstract];

        foreach ($this->extenders[$abstract] as $extender) {
            $resolved = $this->extendConcrete($resolved, $extender);
        }

        if ($binding && $binding[TypesContract::BINDING_TYPE] !== TypesContract::SERVICE) {
            unset($this->extenders[$abstract]);

            $this->bindings[$abstract][TypesContract::VALUE] = $resolved;
        }
    }

    /**
     * Call the given closure.
     *
     * @param mixed    $concrete
     * @param \Closure $closure
     *
     * @return mixed
     */
    protected function extendConcrete($concrete, Closure $closure)
    {
        return $closure($concrete, $this);
    }

    /**
     * Format a class binding.
     *
     * @param \Closure|object|string $implementation
     * @param \ReflectionClass       $parameterClass
     *
     * @return mixed
     */
    protected function contextualBindingFormat($implementation, ReflectionClass $parameterClass)
    {
        if ($implementation instanceof Closure || $implementation instanceof $parameterClass->name) {
            return $implementation;
        }

        return static function (ContainerContract $container) use ($implementation) {
            return $container->resolve($implementation);
        };
    }
}


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

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
use Viserio\Component\Contract\Exception\Displayer as DisplayerContract;
use Viserio\Component\Contract\HttpFactory\Traits\ResponseFactoryAwareTrait;
use Viserio\Component\Exception\ExceptionInfo;

class JsonDisplayer implements DisplayerContract
{
    use ResponseFactoryAwareTrait;

    /**
     * Create a new html displayer instance.
     *
     * @param \Psr\Http\Message\ResponseFactoryInterface $responseFactory
     */
    public function __construct(ResponseFactoryInterface $responseFactory)
    {
        $this->responseFactory = $responseFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function display(Throwable $exception, string $id, int $code, array $headers): ResponseInterface
    {
        $info  = ExceptionInfo::generate($id, $code);
        $error = ['id' => $id, 'status' => $info['code'], 'title' => $info['name'], 'detail' => $info['detail']];

        $response = $this->responseFactory->createResponse($code);

        foreach (\array_merge($headers, ['Content-Type' => $this->getContentType()]) as $header => $value) {
            $response = $response->withAddedHeader($header, $value);
        }

        $body = $response->getBody();
        $body->write(\json_encode(['errors' => [$error]], \JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_UNESCAPED_SLASHES));
        $body->rewind();

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

    /**
     * {@inheritdoc}
     */
    public function getContentType(): string
    {
        return 'application/json';
    }

    /**
     * {@inheritdoc}
     */
    public function canDisplay(Throwable $original, Throwable $transformed, int $code): bool
    {
        return true;
    }

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


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

use Traversable;

trait SimpleTraceableCacheDecoratorTrait
{
    /**
     * Instance of stdClass.
     *
     * @var \stdClass
     */
    private $miss;

    /**
     * {@inheritdoc}
     */
    public function get($key, $default = null)
    {
        $miss  = null !== $default && \is_object($default) ? $default : $this->miss;
        $event = $this->start(__FUNCTION__);

        try {
            $value = $this->pool->get($key, $miss);
        } finally {
            $event->end = \microtime(true);
        }

        if ($event->result[$key] = $miss !== $value) {
            $event->hits++;
        } else {
            $event->misses++;
            $value = $default;
        }

        return $value;
    }

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

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

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

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

    /**
     * {@inheritdoc}
     */
    public function set($key, $value, $ttl = null)
    {
        $event = $this->start(__FUNCTION__);

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

    /**
     * {@inheritdoc}
     */
    public function setMultiple($values, $ttl = null)
    {
        $event                 = $this->start(__FUNCTION__);
        $event->result['keys'] = [];

        if ($values instanceof Traversable) {
            $values = static function () use ($values, $event) {
                foreach ($values as $k => $v) {
                    $event->result['keys'][] = $k;

                    yield $k => $v;
                }
            };
            $values = $values();
        } elseif (\is_array($values)) {
            $event->result['keys'] = \array_keys($values);
        }

        try {
            return $event->result['result'] = $this->pool->setMultiple($values, $ttl);
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getMultiple($keys, $default = null): \Generator
    {
        $miss  = null !== $default && \is_object($default) ? $default : $this->miss;
        $event = $this->start(__FUNCTION__);

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

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

            foreach ($result as $key => $value) {
                if ($event->result[$key] = $miss !== $value) {
                    $event->hits++;
                } else {
                    $event->misses++;
                    $value = $default;
                }

                yield $key => $value;
            }
        };

        return $f();
    }

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

        if ($keys instanceof Traversable) {
            $keys = $event->result['keys'] = \iterator_to_array($keys, false);
        } else {
            $event->result['keys'] = $keys;
        }

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


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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Viserio\Component\Contract\Cookie\QueueingFactory as CookieJar;
use Viserio\Component\Cookie\ResponseCookies;

class AddQueuedCookiesToResponseMiddleware implements MiddlewareInterface
{
    /**
     * The cookie jar instance.
     *
     * @var \Viserio\Component\Contract\Cookie\QueueingFactory
     */
    protected $cookies;

    /**
     * Create a new CookieQueue instance.
     *
     * @param \Viserio\Component\Contract\Cookie\QueueingFactory $cookies
     */
    public function __construct(CookieJar $cookies)
    {
        $this->cookies = $cookies;
    }

    /**
     * {@inheritdoc}
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $response = $handler->handle($request);
        $cookies  = ResponseCookies::fromResponse($response);

        foreach ($this->cookies->getQueuedCookies() as $name => $cookie) {
            $cookies = $cookies->add($cookie);
        }

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


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

use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
use Swift_Mime_Attachment;
use Swift_Mime_Headers_DateHeader;
use Swift_Mime_Headers_IdentificationHeader;
use Swift_Mime_Headers_ParameterizedHeader;
use Swift_Mime_Headers_PathHeader;
use Swift_Mime_Headers_UnstructuredHeader;
use Swift_Mime_SimpleMessage;
use Swift_Mime_SimpleMimeEntity;

class PostmarkTransport extends AbstractTransport
{
    /**
     * Guzzle client instance.
     *
     * @var \GuzzleHttp\Client
     */
    protected $client;

    /**
     * The Postmark Server Token key.
     *
     * @var string
     */
    protected $serverToken;

    /**
     * The Postmark API end-point.
     *
     * @var string
     */
    protected $url = 'https://api.postmarkapp.com/email';

    /**
     * Create a new Postmark transport instance.
     *
     * @param \GuzzleHttp\Client $client
     * @param string             $serverToken
     */
    public function __construct(Client $client, string $serverToken)
    {
        $this->client      = $client;
        $this->serverToken = $serverToken;
    }

    /**
     * Get the API key being used by the transport.
     *
     * @return string
     */
    public function getServerToken(): string
    {
        return $this->serverToken;
    }

    /**
     * Set the API Server Token being used by the transport.
     *
     * @param string $serverToken
     *
     * @return $this
     */
    public function setServerToken(string $serverToken): self
    {
        $this->serverToken = $serverToken;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null): int
    {
        $this->beforeSendPerformed($message);

        $version = \PHP_VERSION ?? 'Unknown PHP version';
        $os      = \PHP_OS      ?? 'Unknown OS';

        $response = $this->client->post(
            $this->url,
            [
                'headers' => [
                    'X-Postmark-Server-Token' => $this->serverToken,
                    'Content-Type'            => 'application/json',
                    'User-Agent'              => "postmark (PHP Version: ${version}, OS: ${os})",
                ],
                'json' => $this->getMessagePayload($message),
            ]
        );

        $message->getHeaders()->addTextHeader(
            'X-PM-Message-Id',
            $this->getMessageId($response)
        );

        $this->sendPerformed($message);

        return $this->numberOfRecipients($message);
    }

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

    /**
     * Get the message ID from the response.
     *
     * @param null|\Psr\Http\Message\ResponseInterface $response
     *
     * @return string
     */
    protected function getMessageId(?ResponseInterface $response): string
    {
        if ($response === null) {
            return '';
        }

        $content = \json_decode($response->getBody()->getContents(), true);

        return $content['MessageID'];
    }

    /**
     * Convert email dictionary with emails and names
     * to array of emails with names.
     *
     * @param string[] $emails
     *
     * @return array
     */
    protected function convertEmailsArray(array $emails): array
    {
        $convertedEmails = [];

        foreach ($emails as $email => $name) {
            $convertedEmails[] = $name ?
            '"' . \str_replace('"', '\\"', $name) . "\" <{$email}>" :
            $email;
        }

        return $convertedEmails;
    }

    /**
     * Gets MIME parts that match the message type.
     * Excludes parts of type \Swift_Mime_Attachment as those
     * are handled later.
     *
     * @param \Swift_Mime_SimpleMessage $message
     * @param string                    $mimeType
     *
     * @return null|\Swift_Mime_SimpleMimeEntity
     */
    protected function getMIMEPart(Swift_Mime_SimpleMessage $message, $mimeType): ?Swift_Mime_SimpleMimeEntity
    {
        foreach ($message->getChildren() as $part) {
            if (! ($part instanceof Swift_Mime_Attachment) && \mb_strpos($part->getContentType(), $mimeType) === 0) {
                return $part;
            }
        }

        return null;
    }

    /**
     * Convert a Swift Mime Message to a Postmark Payload.
     *
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return array
     */
    protected function getMessagePayload(Swift_Mime_SimpleMessage $message): array
    {
        $payload = [];

        $payload = $this->processRecipients($payload, $message);
        $payload = $this->processMessageParts($payload, $message);

        return $this->processHeaders($payload, $message);
    }

    /**
     * Applies the recipients of the message into the API Payload.
     *
     * @param array                     $payload
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return array
     */
    protected function processRecipients(array $payload, Swift_Mime_SimpleMessage $message): array
    {
        $payload['From']    = \implode(',', $this->convertEmailsArray($message->getFrom()));
        $payload['To']      = \implode(',', $this->convertEmailsArray($message->getTo()));
        $payload['Subject'] = $message->getSubject() ?? '';

        $tags = $message->getHeaders()->getAll('tag');
        /** @var \Swift_Mime_Header $tag */
        $tag = \end($tags);

        $payload['Tag'] = $tag !== false ? $tag->getFieldBody() : '';

        $cc = $message->getCc();

        if (\is_array($cc)) {
            $payload['Cc'] = \implode(',', $this->convertEmailsArray($cc));
        }

        $replyTo = $message->getReplyTo();

        if (\is_string($replyTo)) {
            $payload['ReplyTo'] = $replyTo;
        }

        $bcc = $message->getBcc();

        if (\is_array($bcc)) {
            $payload['Bcc'] = \implode(',', $this->convertEmailsArray($bcc));
        }

        return $payload;
    }

    /**
     * Applies the message parts and attachments
     * into the API Payload.
     *
     * @param array                     $payload
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return array
     */
    protected function processMessageParts(array $payload, Swift_Mime_SimpleMessage $message): array
    {
        //Get the primary message.
        switch ($message->getContentType()) {
            case 'text/html':
            case 'multipart/mixed':
            case 'multipart/related':
            case 'multipart/alternative':
                $payload['HtmlBody'] = $message->getBody();

                break;

            default:
                $payload['TextBody'] = $message->getBody();

                break;
        }

        // Provide an alternate view from the secondary parts.
        if ($plain = $this->getMIMEPart($message, 'text/plain')) {
            $payload['TextBody'] = $plain->getBody();
        }

        if ($html = $this->getMIMEPart($message, 'text/html')) {
            $payload['HtmlBody'] = $html->getBody();
        }

        if ($message->getChildren()) {
            $payload['Attachments'] = [];

            foreach ($message->getChildren() as $attachment) {
                if (\is_object($attachment) && $attachment instanceof Swift_Mime_Attachment) {
                    $attachments = [
                        'Name'        => $attachment->getFilename(),
                        'Content'     => \base64_encode($attachment->getBody()),
                        'ContentType' => $attachment->getContentType(),
                    ];

                    if ($attachment->getDisposition() !== 'attachment' &&
                        $attachment->getId() !== null
                    ) {
                        $attachments['ContentID'] = 'cid:' . $attachment->getId();
                    }

                    $payload['Attachments'][] = $attachments;
                }
            }
        }

        return $payload;
    }

    /**
     * Applies the headers into the API Payload.
     *
     * @param array                     $payload
     * @param \Swift_Mime_SimpleMessage $message
     *
     * @return array
     */
    protected function processHeaders(array $payload, Swift_Mime_SimpleMessage $message): array
    {
        $headers = [];

        foreach ($message->getHeaders()->getAll() as $key => $value) {
            $fieldName       = $value->getFieldName();
            $excludedHeaders = ['Subject', 'Content-Type', 'MIME-Version', 'Date'];

            if (! \in_array($fieldName, $excludedHeaders, true)) {
                if ($value instanceof Swift_Mime_Headers_UnstructuredHeader) {
                    $headers[] = [
                        'Name'  => $fieldName,
                        'Value' => $value->getValue(),
                    ];
                } elseif ($value instanceof Swift_Mime_Headers_DateHeader ||
                    $value instanceof Swift_Mime_Headers_IdentificationHeader ||
                    $value instanceof Swift_Mime_Headers_ParameterizedHeader ||
                    $value instanceof Swift_Mime_Headers_PathHeader
                ) {
                    $headers[] = [
                        'Name'  => $fieldName,
                        'Value' => $value->getFieldBody(),
                    ];

                    if ($value->getFieldName() === 'Message-ID') {
                        $headers[] = [
                            'Name'  => 'X-PM-KeepID',
                            'Value' => 'true',
                        ];
                    }
                }
            }
        }

        $payload['Headers'] = $headers;

        return $payload;
    }
}


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

/**
 * @method null|array|string option($key = null)
 * @method void              error(string $string, $verbosityLevel = null)
 * @method bool              hasOption(string $key)
 */
trait ServerCommandRequirementsCheckTrait
{
    /**
     * The document (public folder) for the server.
     *
     * @var null|string
     */
    private $documentRoot;

    /**
     * The env for the server.
     *
     * @var null|string
     */
    private $environment;

    /**
     * Check the requirements for the command.
     *
     * @return int
     */
    protected function checkRequirements(): int
    {
        if ($this->documentRoot === null) {
            if ($this->hasOption('docroot')) {
                $this->documentRoot = $this->option('docroot');
            } else {
                $this->error('The document root directory must be either passed as first argument of the constructor or through the "--docroot" input option.');

                return 1;
            }
        }

        if ($this->environment === null) {
            if ($this->hasOption('env')) {
                if (\is_string($env = $this->option('env'))) {
                    $this->environment = $env;
                } else {
                    $this->error('The environment must be either passed as second argument of the constructor or through the "--env" input option.');

                    return 1;
                }
            } else {
                $this->error('The environment must be passed as second argument of the constructor.');

                return 1;
            }
        }

        if ($this->environment === 'prod') {
            $this->error('Running this server in production environment is NOT recommended!');
        }

        return 0;
    }
}