<?php
//TODO: Return to this validator after ActiveRecord development

namespace Reaction\Validators;

use Reaction;
use Reaction\Exceptions\Error;
use Reaction\Exceptions\InvalidConfigException;
use Reaction\Base\Model;
use Reaction\Db\Orm\ActiveQuery;
use Reaction\Db\Orm\ActiveRecord;
use Reaction\Db\QueryInterface;
use Reaction\I18n\I18N;
use Reaction\Promise\ExtendedPromiseInterface;
use function Reaction\Promise\reject;

/**
 * ExistValidator validates that the attribute value exists in a table.
 *
 * ExistValidator checks if the value being validated can be found in the table column specified by
 * the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]].
 *
 * This validator is often used to verify that a foreign key contains a value
 * that can be found in the foreign table.
 *
 * The following are examples of validation rules using this validator:
 *
 * ```php
 * // a1 needs to exist
 * ['a1', 'exist']
 * // a1 needs to exist, but its value will use a2 to check for the existence
 * ['a1', 'exist', 'targetAttribute' => 'a2']
 * // a1 and a2 need to exist together, and they both will receive error message
 * [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']]
 * // a1 and a2 need to exist together, only a1 will receive error message
 * ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']]
 * // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value)
 * ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']]
 * ```
 */
class ExistValidator extends Validator
{
    /**
     * @var string the name of the ActiveRecord class that should be used to validate the existence
     * of the current attribute value. If not set, it will use the ActiveRecord class of the attribute being validated.
     * @see targetAttribute
     */
    public $targetClass;
    /**
     * @var string|array the name of the ActiveRecord attribute that should be used to
     * validate the existence of the current attribute value. If not set, it will use the name
     * of the attribute currently being validated. You may use an array to validate the existence
     * of multiple columns at the same time. The array key is the name of the attribute with the value to validate,
     * the array value is the name of the database field to search.
     */
    public $targetAttribute;
    /**
     * @var string the name of the relation that should be used to validate the existence of the current attribute value
     * This param overwrites $targetClass and $targetAttribute
     */
    public $targetRelation;
    /**
     * @var string|array|\Closure additional filter to be applied to the DB query used to check the existence of the attribute value.
     * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
     * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
     * is the [[\yii\db\Query|Query]] object that you can modify in the function.
     */
    public $filter;
    /**
     * @var bool whether to allow array type attribute.
     */
    public $allowArray = false;
    /**
     * @var string and|or define how target attributes are related
     */
    public $targetAttributeJunction = 'and';
    /**
     * @var bool whether this validator is forced to always use master DB
     */
    public $forceMasterDb = true;

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();
        if ($this->message === null) {
            $this->message = Reaction::t('rct', '{attribute} is invalid.');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function validateAttribute($model, $attribute)
    {
        if (!empty($this->targetRelation)) {
            return $this->checkTargetRelationExistence($model, $attribute);
        } else {
            return $this->checkTargetAttributeExistence($model, $attribute);
        }
    }

    /**
     * Validates existence of the current attribute based on relation name
     * @param \Reaction\Db\Orm\ActiveRecord $model the data model to be validated
     * @param string                        $attribute the name of the attribute to be validated.
     * @return ExtendedPromiseInterface
     */
    private function checkTargetRelationExistence($model, $attribute)
    {
        /** @var ActiveQuery $relationQuery */
        $relationQuery = $model->{'get' . ucfirst($this->targetRelation)}();

        if ($this->filter instanceof \Closure) {
            call_user_func($this->filter, $relationQuery);
        } elseif ($this->filter !== null) {
            $relationQuery->andWhere($this->filter);
        }

        return $relationQuery->exists()
            ->then(null, function() use ($model, $attribute) {
                $this->addError($model, $attribute, $this->message);
                return null;
            });
    }

    /**
     * Validates existence of the current attribute based on targetAttribute
     * @param \Reaction\Base\Model $model the data model to be validated
     * @param string $attribute the name of the attribute to be validated.
     */
    private function checkTargetAttributeExistence($model, $attribute)
    {
        $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
        $params = $this->prepareConditions($targetAttribute, $model, $attribute);
        $conditions = [$this->targetAttributeJunction == 'or' ? 'or' : 'and'];

        if (!$this->allowArray) {
            foreach ($params as $key => $value) {
                if (is_array($value)) {
                    $this->addError($model, $attribute, Reaction::t('rct', '{attribute} is invalid.'));

                    return null;
                }
                $conditions[] = [$key => $value];
            }
        } else {
            $conditions[] = $params;
        }

        $targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass;
        $query = $this->createQuery($targetClass, $conditions);

        return $this->valueExists($query, $model->$attribute)
            ->then(null, function() use (&$model, $attribute) {
                $this->addError($model, $attribute, $this->message);
                return null;
            });
    }

    /**
     * Processes attributes' relations described in $targetAttribute parameter into conditions, compatible with
     * [[\yii\db\Query::where()|Query::where()]] key-value format.
     *
     * @param $targetAttribute array|string $attribute the name of the ActiveRecord attribute that should be used to
     * validate the existence of the current attribute value. If not set, it will use the name
     * of the attribute currently being validated. You may use an array to validate the existence
     * of multiple columns at the same time. The array key is the name of the attribute with the value to validate,
     * the array value is the name of the database field to search.
     * If the key and the value are the same, you can just specify the value.
     * @param \Reaction\Base\Model $model the data model to be validated
     * @param string $attribute the name of the attribute to be validated in the $model
     * @return array conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
     * @throws InvalidConfigException
     */
    private function prepareConditions($targetAttribute, $model, $attribute)
    {
        if (is_array($targetAttribute)) {
            if ($this->allowArray) {
                throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
            }
            $conditions = [];
            foreach ($targetAttribute as $k => $v) {
                $conditions[$v] = is_int($k) ? $model->$v : $model->$k;
            }
        } else {
            $conditions = [$targetAttribute => $model->$attribute];
        }

        $targetModelClass = $this->getTargetClass($model);
        if (!is_subclass_of($targetModelClass, 'yii\db\ActiveRecord')) {
            return $conditions;
        }

        /** @var ActiveRecord $targetModelClass */
        return $this->applyTableAlias($targetModelClass::find(), $conditions);
    }

    /**
     * @param Model $model the data model to be validated
     * @return string Target class name
     */
    private function getTargetClass($model)
    {
        return $this->targetClass === null ? get_class($model) : $this->targetClass;
    }

    /**
     * {@inheritdoc}
     */
    protected function validateValue($value)
    {
        if ($this->targetClass === null) {
            throw new InvalidConfigException('The "targetClass" property must be set.');
        }
        if (!is_string($this->targetAttribute)) {
            throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
        }

        if (is_array($value) && !$this->allowArray) {
            return [$this->message, []];
        }

        $query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]);

        return $this->valueExists($query, $value)
            ->then(null, function() {
                return [$this->message, []];
            });
    }

    /**
     * Check whether value exists in target table
     *
     * @param QueryInterface $query
     * @param mixed $value the value want to be checked
     * @return ExtendedPromiseInterface
     */
    private function valueExists($query, $value)
    {
        if (is_array($value)) {
            return $query->count("DISTINCT [[$this->targetAttribute]]")
                ->then(function($count) use ($value) {
                    return $count == count($value) ? true : reject(new Error("Not exists (count mismatch)"));
                });
        }
        return $query->exists();
    }

    /**
     * Creates a query instance with the given condition.
     * @param string $targetClass the target AR class
     * @param mixed $condition query condition
     * @return \Reaction\Db\Orm\ActiveQueryInterface the query instance
     */
    protected function createQuery($targetClass, $condition)
    {
        /* @var $targetClass \Reaction\Db\Orm\ActiveRecordInterface */
        $query = $targetClass::find()->andWhere($condition);
        if ($this->filter instanceof \Closure) {
            call_user_func($this->filter, $query);
        } elseif ($this->filter !== null) {
            $query->andWhere($this->filter);
        }

        return $query;
    }

    /**
     * Returns conditions with alias.
     * @param ActiveQuery $query
     * @param array $conditions array of condition, keys to be modified
     * @param null|string $alias set empty string for no apply alias. Set null for apply primary table alias
     * @return array
     */
    private function applyTableAlias($query, $conditions, $alias = null)
    {
        if ($alias === null) {
            $alias = array_keys($query->getTablesUsedInFrom())[0];
        }
        $prefixedConditions = [];
        foreach ($conditions as $columnName => $columnValue) {
            if (strpos($columnName, '(') === false) {
                $prefixedColumn = "{$alias}.[[" . preg_replace(
                    '/^' . preg_quote($alias) . '\.(.*)$/',
                    '$1',
                    $columnName) . ']]';
            } else {
                // there is an expression, can't prefix it reliably
                $prefixedColumn = $columnName;
            }

            $prefixedConditions[$prefixedColumn] = $columnValue;
        }

        return $prefixedConditions;
    }
}


<?php

namespace Simply\Container\Entry;

use Psr\Container\ContainerInterface;

/**
 * Represents an entry that is wired based on constructor arguments.
 * @author Riikka Kalliomäki <riikka.kalliomaki@gmail.com>
 * @copyright Copyright (c) 2018 Riikka Kalliomäki
 * @license http://opensource.org/licenses/mit-license.php MIT License
 */
class WiredEntry implements EntryInterface
{
    /** @var string The name of the class to instantiate */
    private $class;

    /** @var string[] The identifiers of the container entries to pass to the constructor */
    private $arguments;

    /**
     * WiredEntry constructor.
     * @param string $class The name of the class to instantiate
     * @param string[] $arguments The identifiers of the container entries to pass to the constructor
     */
    public function __construct(string $class, array $arguments)
    {
        $this->class = $class;
        $this->setArguments(... $arguments);
    }

    /**
     * Sets the identifiers for constructor arguments for the wired entry.
     * @param string ...$arguments The identifiers for constructor arguments
     */
    private function setArguments(string ... $arguments): void
    {
        $this->arguments = $arguments;
    }

    /** {@inheritdoc} */
    public static function createFromCacheParameters(array $parameters): EntryInterface
    {
        [
            $class,
            $arguments,
        ] = $parameters;

        return new static($class, $arguments);
    }

    /** {@inheritdoc} */
    public function getCacheParameters(): array
    {
        return [
            $this->class,
            $this->arguments,
        ];
    }

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

    /** {@inheritdoc} */
    public function getValue(ContainerInterface $container)
    {
        $values = array_map(function (string $name) use ($container) {
            return $container->get($name);
        }, $this->arguments);

        return new $this->class(... $values);
    }
}


<?php

namespace Ekyna\Bundle\CoreBundle\Modal;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * Class Renderer
 * @package Ekyna\Bundle\CoreBundle\Modal
 * @author Étienne Dauvergne <contact@ekyna.com>
 */
class Renderer
{
    /**
     * @var \Twig_Environment
     */
    protected $twig;

    /**
     * @var TranslatorInterface
     */
    protected $translator;

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

    /**
     * Constructor.
     *
     * @param \Twig_Environment   $twig
     * @param TranslatorInterface $translator
     * @param string              $charset
     */
    public function __construct(\Twig_Environment $twig, TranslatorInterface $translator, $charset)
    {
        $this->twig       = $twig;
        $this->translator = $translator;
        $this->charset    = $charset;
    }

    /**
     * Renders and returns the modal response.
     *
     * @param Modal $modal
     * @param string $template
     * @return Response
     */
    public function render(Modal $modal, $template = 'EkynaCoreBundle:Modal:modal.xml.twig')
    {
        $response = new Response();

        // Translations
        $modal->setTitle($this->translator->trans($modal->getTitle()));
        $buttons = $modal->getButtons();
        foreach($buttons as &$button) {
            $button['label'] = $this->translator->trans($button['label']);
        }
        $modal->setButtons($buttons);

        $response->setContent($this->twig->render($template, ['modal' => $modal]));

        $response->headers->add(['Content-Type' => 'application/xml; charset='.strtolower($this->charset)]);

        return $response;
    }
}


<?php

namespace Reaction\Helpers\Request;

use Reaction\Exceptions\InvalidArgumentException;

/**
 * Class FileHelper. Proxy to \Reaction\Helpers\FileHelper
 * @package Reaction\Helpers\Request
 */
class FileHelper extends RequestAppHelperProxy
{
    public $helperClass = 'Reaction\Helpers\FileHelper';

    /**
     * Normalizes a file/directory path.
     *
     * The normalization does the following work:
     *
     * - Convert all directory separators into `DIRECTORY_SEPARATOR` (e.g. "\a/b\c" becomes "/a/b/c")
     * - Remove trailing directory separators (e.g. "/a/b/c/" becomes "/a/b/c")
     * - Turn multiple consecutive slashes into a single one (e.g. "/a///b/c" becomes "/a/b/c")
     * - Remove ".." and "." based on their meanings (e.g. "/a/./b/../c" becomes "/a/c")
     *
     * @param string $path the file/directory path to be normalized
     * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
     * @return string the normalized file/directory path
     * @see \Reaction\Helpers\FileHelper::normalizePath()
     */
    public function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
    {
        return $this->proxy(__FUNCTION__, [$path, $ds]);
    }

    /**
     * Returns the localized version of a specified file.
     *
     * The searching is based on the specified language code. In particular,
     * a file with the same name will be looked for under the subdirectory
     * whose name is the same as the language code. For example, given the file "path/to/view.php"
     * and language code "zh-CN", the localized file will be looked for as
     * "path/to/zh-CN/view.php". If the file is not found, it will try a fallback with just a language code that is
     * "zh" i.e. "path/to/zh/view.php". If it is not found as well the original file will be returned.
     *
     * If the target and the source language codes are the same,
     * the original file will be returned.
     *
     * @param string $file the original file
     * @param string $language the target language that the file should be localized to.
     * If not set, the value of [[\yii\base\Application::language]] will be used.
     * @param string $sourceLanguage the language that the original file is in.
     * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used.
     * @return string the matching localized file, or the original file if the localized version is not found.
     * If the target and the source language codes are the same, the original file will be returned.
     * @see \Reaction\Helpers\FileHelper::localize()
     */
    public function localize($file, $language = null, $sourceLanguage = null)
    {
        return $this->proxyWithLanguage(__FUNCTION__, [$file, $language, $sourceLanguage], -2);
    }

    /**
     * Determines the MIME type of the specified file.
     * This method will first try to determine the MIME type based on
     * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If the `fileinfo` extension is not installed,
     * it will fall back to [[getMimeTypeByExtension()]] when `$checkExtension` is true.
     * @param string $file the file name.
     * @param string $magicFile name of the optional magic database file (or alias), usually something like `/path/to/magic.mime`.
     * This will be passed as the second parameter to [finfo_open()](http://php.net/manual/en/function.finfo-open.php)
     * when the `fileinfo` extension is installed. If the MIME type is being determined based via [[getMimeTypeByExtension()]]
     * and this is null, it will use the file specified by [[mimeMagicFile]].
     * @param bool $checkExtension whether to use the file extension to determine the MIME type in case
     * `finfo_open()` cannot determine it.
     * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined.
     * @see \Reaction\Helpers\FileHelper::getMimeType()
     */
    public function getMimeType($file, $magicFile = null, $checkExtension = true)
    {
        return $this->proxy(__FUNCTION__, [$file, $magicFile, $checkExtension]);
    }

    /**
     * Determines the MIME type based on the extension name of the specified file.
     * This method will use a local map between extension names and MIME types.
     * @param string $file the file name.
     * @param string $magicFile the path (or alias) of the file that contains all available MIME type information.
     * If this is not set, the file specified by [[mimeMagicFile]] will be used.
     * @return string|null the MIME type. Null is returned if the MIME type cannot be determined.
     * @see \Reaction\Helpers\FileHelper::getMimeTypeByExtension()
     */
    public function getMimeTypeByExtension($file, $magicFile = null)
    {
        return $this->proxy(__FUNCTION__, [$file, $magicFile]);
    }

    /**
     * Determines the extensions by given MIME type.
     * This method will use a local map between extension names and MIME types.
     * @param string $mimeType file MIME type.
     * @param string $magicFile the path (or alias) of the file that contains all available MIME type information.
     * If this is not set, the file specified by [[mimeMagicFile]] will be used.
     * @return array the extensions corresponding to the specified MIME type
     * @see \Reaction\Helpers\FileHelper::getExtensionsByMimeType()
     */
    public function getExtensionsByMimeType($mimeType, $magicFile = null)
    {
        return $this->proxy(__FUNCTION__, [$mimeType, $magicFile]);
    }

    /**
     * Loads MIME types from the specified file.
     * @param string $magicFile the path (or alias) of the file that contains all available MIME type information.
     * If this is not set, the file specified by [[mimeMagicFile]] will be used.
     * @return array the mapping from file extensions to MIME types
     */
    protected function loadMimeTypes($magicFile)
    {
        return $this->proxy(__FUNCTION__, [$magicFile]);
    }

    /**
     * Loads MIME aliases from the specified file.
     * @param string $aliasesFile the path (or alias) of the file that contains MIME type aliases.
     * If this is not set, the file specified by [[mimeAliasesFile]] will be used.
     * @return array the mapping from file extensions to MIME types
     */
    protected function loadMimeAliases($aliasesFile)
    {
        return $this->proxy(__FUNCTION__, [$aliasesFile]);
    }

    /**
     * Copies a whole directory as another one.
     * The files and sub-directories will also be copied over.
     * @param string $src the source directory
     * @param string $dst the destination directory
     * @param array $options options for directory copy. Valid options are:
     *
     * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775.
     * - fileMode:  integer, the permission to be set for newly copied files. Defaults to the current environment setting.
     * - filter: callback, a PHP callback that is called for each directory or file.
     *   The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
     *   The callback can return one of the following values:
     *
     *   * true: the directory or file will be copied (the "only" and "except" options will be ignored)
     *   * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored)
     *   * null: the "only" and "except" options will determine whether the directory or file should be copied
     *
     * - only: array, list of patterns that the file paths should match if they want to be copied.
     *   A path matches a pattern if it contains the pattern string at its end.
     *   For example, '.php' matches all file paths ending with '.php'.
     *   Note, the '/' characters in a pattern matches both '/' and '\' in the paths.
     *   If a file path matches a pattern in both "only" and "except", it will NOT be copied.
     * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied.
     *   A path matches a pattern if it contains the pattern string at its end.
     *   Patterns ending with '/' apply to directory paths only, and patterns not ending with '/'
     *   apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b';
     *   and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches
     *   both '/' and '\' in the paths.
     * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true.
     * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
     * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
     *   If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
     *   The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
     *   file to be copied from, while `$to` is the copy target.
     * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
     *   The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
     *   file copied from, while `$to` is the copy target.
     * - copyEmptyDirectories: boolean, whether to copy empty directories. Set this to false to avoid creating directories
     *   that do not contain files. This affects directories that do not contain files initially as well as directories that
     *   do not contain files at the target destination because files have been filtered via `only` or `except`.
     *   Defaults to true. This option is available since version 2.0.12. Before 2.0.12 empty directories are always copied.
     * @throws InvalidArgumentException if unable to open directory
     * @see \Reaction\Helpers\FileHelper::copyDirectory()
     */
    public function copyDirectory($src, $dst, $options = [])
    {
        $this->proxy(__FUNCTION__, [$src, $dst, $options]);
    }

    /**
     * Removes a directory (and all its content) recursively.
     *
     * @param string $dir the directory to be deleted recursively.
     * @param array $options options for directory remove. Valid options are:
     *
     * - traverseSymlinks: boolean, whether symlinks to the directories should be traversed too.
     *   Defaults to `false`, meaning the content of the symlinked directory would not be deleted.
     *   Only symlink would be removed in that default case.
     *
     * @see \Reaction\Helpers\FileHelper::removeDirectory()
     */
    public function removeDirectory($dir, $options = [])
    {
        $this->proxy(__FUNCTION__, [$dir, $options]);
    }

    /**
     * Removes a file or symlink in a cross-platform way
     *
     * @param string $path
     * @return bool
     * @see \Reaction\Helpers\FileHelper::unlink()
     */
    public function unlink($path)
    {
        return $this->proxy(__FUNCTION__, [$path]);
    }

    /**
     * Returns the files found under the specified directory and subdirectories.
     * @param string $dir the directory under which the files will be looked for.
     * @param array $options options for file searching. Valid options are:
     *
     * - `filter`: callback, a PHP callback that is called for each directory or file.
     *   The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
     *   The callback can return one of the following values:
     *
     *   * `true`: the directory or file will be returned (the `only` and `except` options will be ignored)
     *   * `false`: the directory or file will NOT be returned (the `only` and `except` options will be ignored)
     *   * `null`: the `only` and `except` options will determine whether the directory or file should be returned
     *
     * - `except`: array, list of patterns excluding from the results matching file or directory paths.
     *   Patterns ending with slash ('/') apply to directory paths only, and patterns not ending with '/'
     *   apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b';
     *   and `.svn/` matches directory paths ending with `.svn`.
     *   If the pattern does not contain a slash (`/`), it is treated as a shell glob pattern
     *   and checked for a match against the pathname relative to `$dir`.
     *   Otherwise, the pattern is treated as a shell glob suitable for consumption by `fnmatch(3)`
     *   with the `FNM_PATHNAME` flag: wildcards in the pattern will not match a `/` in the pathname.
     *   For example, `views/*.php` matches `views/index.php` but not `views/controller/index.php`.
     *   A leading slash matches the beginning of the pathname. For example, `/*.php` matches `index.php` but not `views/start/index.php`.
     *   An optional prefix `!` which negates the pattern; any matching file excluded by a previous pattern will become included again.
     *   If a negated pattern matches, this will override lower precedence patterns sources. Put a backslash (`\`) in front of the first `!`
     *   for patterns that begin with a literal `!`, for example, `\!important!.txt`.
     *   Note, the '/' characters in a pattern matches both '/' and '\' in the paths.
     * - `only`: array, list of patterns that the file paths should match if they are to be returned. Directory paths
     *   are not checked against them. Same pattern matching rules as in the `except` option are used.
     *   If a file path matches a pattern in both `only` and `except`, it will NOT be returned.
     * - `caseSensitive`: boolean, whether patterns specified at `only` or `except` should be case sensitive. Defaults to `true`.
     * - `recursive`: boolean, whether the files under the subdirectories should also be looked for. Defaults to `true`.
     * @return array files found under the directory, in no particular order. Ordering depends on the files system used.
     * @throws InvalidArgumentException if the dir is invalid.
     * @see \Reaction\Helpers\FileHelper::findFiles()
     */
    public function findFiles($dir, $options = [])
    {
        return $this->proxy(__FUNCTION__, [$dir, $options]);
    }

    /**
     * Returns the directories found under the specified directory and subdirectories.
     * @param string $dir the directory under which the files will be looked for.
     * @param array $options options for directory searching. Valid options are:
     *
     * - `filter`: callback, a PHP callback that is called for each directory or file.
     *   The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
     *   The callback can return one of the following values:
     *
     *   * `true`: the directory will be returned
     *   * `false`: the directory will NOT be returned
     *
     * - `recursive`: boolean, whether the files under the subdirectories should also be looked for. Defaults to `true`.
     * @return array directories found under the directory, in no particular order. Ordering depends on the files system used.
     * @throws InvalidArgumentException if the dir is invalid.
     * @see \Reaction\Helpers\FileHelper::findDirectories()
     */
    public function findDirectories($dir, $options = [])
    {
        return $this->proxy(__FUNCTION__, [$dir, $options]);
    }

    /**
     * Checks if the given file path satisfies the filtering options.
     * @param string $path the path of the file or directory to be checked
     * @param array $options the filtering options. See [[findFiles()]] for explanations of
     * the supported options.
     * @return bool whether the file or directory satisfies the filtering options.
     * @see \Reaction\Helpers\FileHelper::filterPath()
     */
    public function filterPath($path, $options)
    {
        return $this->proxy(__FUNCTION__, [$path, $options]);
    }

    /**
     * Creates a new directory.
     *
     * This method is similar to the PHP `mkdir()` function except that
     * it uses `chmod()` to set the permission of the created directory
     * in order to avoid the impact of the `umask` setting.
     *
     * @param string $path path of the directory to be created.
     * @param int $mode the permission to be set for the created directory.
     * @param bool $recursive whether to create parent directories if they do not exist.
     * @return bool whether the directory is created successfully
     * @see \Reaction\Helpers\FileHelper::createDirectory()
     */
    public function createDirectory($path, $mode = 0775, $recursive = true)
    {
        return $this->proxy(__FUNCTION__, [$path, $mode, $recursive]);
    }
}


<?php
namespace keeko\auth\action;

use keeko\framework\domain\payload\Found;
use keeko\framework\foundation\AbstractAction;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Session Status
 *
 * This code is automatically created. Modifications will probably be overwritten.
 */
class StatusAction extends AbstractAction {

	/**
	 * Automatically generated run method
	 *
	 * @param Request $request
	 * @return Response
	 */
	public function run(Request $request) {
		$session = $this->getServiceContainer()->getAuthManager()->getSession();
		$payload = new Found(['model' => $session]);
		// note: $session must be not null for this action!
		return $this->responder->run($request, $payload);
	}
}


<?php

namespace QCharts\CoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use QCharts\CoreBundle\Entity\User\QChartsSubjectInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\JoinTable;
use Doctrine\ORM\Mapping\JoinColumns;

/**
 * QueryRequest
 *
 * @ORM\Table()
 * @ORM\Entity
 * @ORM\Entity(repositoryClass="QCharts\CoreBundle\Repository\QueryRepository")
 */
class QueryRequest
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     *
     * @Assert\NotBlank()
     */
    private $title;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="dateCreated", type="datetimetz")
     */
    private $dateCreated;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="dateLastModified", type="datetimetz")
     */
    private $dateLastModified;

    /**
     * @var string
     * @ORM\Column(name="description", type="text")
     */
    private $description;

    /**
     * @ORM\OneToOne(targetEntity = "Query")
     * @ORM\JoinColumn(name="query_id", referencedColumnName="id")
     *
     */
    private $query;

    /**
     *
     * @ORM\ManyToOne(targetEntity = "\QCharts\CoreBundle\Entity\User\QChartsSubjectInterface")
     * @var QChartsSubjectInterface
     */
    private $createdBy;

    /**
     * @ORM\ManyToOne(targetEntity = "\QCharts\CoreBundle\Entity\User\QChartsSubjectInterface")
     * @var QChartsSubjectInterface
     */
    private $modifiedLastBy;
    /**
     *
     * @ORM\OneToOne(targetEntity = "ChartConfig")
     *
     */
    private $config;

    /**
     * @var Directory
     * @ORM\ManyToOne(targetEntity = "QCharts\CoreBundle\Entity\Directory")
     */
    private $directory;

    /**
     * @var string
     * @ORM\Column(name="cron_expr", type="string", length=255)
     */
    private $cronExpression;

    /**
     * @var QChartsSubjectInterface
     * @ORM\ManyToMany(targetEntity="\QCharts\CoreBundle\Entity\User\QChartsSubjectInterface")
     * @JoinTable(
     *     name="favorites_user",
     *     joinColumns={@JoinColumn(name="query_request_id", referencedColumnName="id")},
     *     inverseJoinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}
     *     )
     */
    private $favoritedBy;

    /**
     * QueryRequest constructor.
     */
    public function __construct()
    {
        $this->favoritedBy = new ArrayCollection();
        $this->dateCreated = new \DateTime('now');
        $this->dateLastModified = new \DateTime('now');
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     *
     * @return QueryRequest
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set dateCreated
     *
     * @param \DateTime $dateCreated
     *
     * @return QueryRequest
     */
    public function setDateCreated($dateCreated = null)
    {
        if (is_null($dateCreated))
        {
            $this->dateCreated = new \DateTime('now');
            return $this;
        }
        $this->dateCreated = $dateCreated;
        return $this;
    }

    /**
     * Get dateCreated
     *
     * @return \DateTime
     */
    public function getDateCreated()
    {
        return $this->dateCreated;
    }

    /**
     * Set query
     *
     * @param Query $query
     *
     * @return QueryRequest
     */
    public function setQuery(Query $query = null)
    {
        $this->query = $query;

        return $this;
    }

    /**
     * Get query
     *
     * @return Query
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * Set createdBy
     *
     * @param \QCharts\CoreBundle\Entity\User\QChartsSubjectInterface $createdBy
     *
     * @return QueryRequest
     */
    public function setCreatedBy(QChartsSubjectInterface $createdBy = null)
    {
        $this->createdBy = $createdBy;

        return $this;
    }

    /**
     * Get createdBy
     *
     * @return QChartsSubjectInterface
     */
    public function getCreatedBy()
    {
        return $this->createdBy;
    }

    /**
     * Set description
     *
     * @param string $description
     *
     * @return QueryRequest
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get description
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set config
     *
     * @param ChartConfig $config
     *
     * @return QueryRequest
     */
    public function setConfig(ChartConfig $config = null)
    {
        $this->config = $config;

        return $this;
    }

    /**
     * Get config
     *
     * @return ChartConfig
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Set dateLastModified
     *
     * @param \DateTime $dateLastModified
     *
     * @return QueryRequest
     */
    public function setDateLastModified($dateLastModified = null)
    {
        if (is_null($dateLastModified))
        {
            $this->dateLastModified = new \DateTime('now');
            return $this;
        }

        $this->dateLastModified = $dateLastModified;
        return $this;
    }

    /**
     * Get dateLastModified
     *
     * @return \DateTime
     */
    public function getDateLastModified()
    {
        return $this->dateLastModified;
    }

    /**
     * Set modifiedLastBy
     *
     * @param QChartsSubjectInterface $modifiedLastBy
     *
     * @return QueryRequest
     */
    public function setModifiedLastBy(QChartsSubjectInterface $modifiedLastBy = null)
    {
        $this->modifiedLastBy = $modifiedLastBy;

        return $this;
    }

    /**
     * Get modifiedLastBy
     *
     * @return QChartsSubjectInterface
     */
    public function getModifiedLastBy()
    {
        return $this->modifiedLastBy;
    }

    /**
     * Set directory
     *
     * @param \QCharts\CoreBundle\Entity\Directory $directory
     *
     * @return QueryRequest
     */
    public function setDirectory(Directory $directory = null)
    {
        $this->directory = $directory;

        return $this;
    }

    /**
     * Get directory
     *
     * @return \QCharts\CoreBundle\Entity\Directory
     */
    public function getDirectory()
    {
        return $this->directory;
    }

    /**
     * Set cronExpression
     *
     * @param string $cronExpression
     *
     * @return QueryRequest
     */
    public function setCronExpression($cronExpression = null)
    {
        $this->cronExpression = $cronExpression;

        return $this;
    }

    /**
     * Get cronExpression
     *
     * @return string
     */
    public function getCronExpression()
    {
        return $this->cronExpression;
    }

    /**
     * Add favorite
     *
     * @param \QCharts\CoreBundle\Entity\User\QChartsSubjectInterface $user
     *
     * @return QueryRequest
     */
    public function addFavoritedBy(QChartsSubjectInterface $user)
    {
        $this->favoritedBy[] = $user;

        return $this;
    }

    /**
     * Remove favorite
     *
     * @param QChartsSubjectInterface $user
     */
    public function removeFavoritedBy(QChartsSubjectInterface $user)
    {
        $this->favoritedBy->removeElement($user);
    }

    /**
     * Get favorites
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getFavoritedBy()
    {
        return $this->favoritedBy;
    }
}


<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja\Http;

use Valkyrja\Application;
use Valkyrja\Support\Collection;
use Valkyrja\Support\Providers\Provides;

/**
 * Class Request.
 *
 * @author Melech Mizrachi
 */
class NativeRequest implements Request
{
    use Provides;

    protected static $httpMethodParameterOverride = false;

    /**
     * Custom parameters.
     *
     * @var \Valkyrja\Support\Collection
     */
    protected $attributes;

    /**
     * Request body parameters ($_POST).
     *
     * @var \Valkyrja\Support\Collection
     */
    protected $request;

    /**
     * Query string parameters ($_GET).
     *
     * @var \Valkyrja\Http\Query
     */
    protected $query;

    /**
     * Server and execution environment parameters ($_SERVER).
     *
     * @var \Valkyrja\Http\Server
     */
    protected $server;

    /**
     * Uploaded files ($_FILES).
     *
     * @var \Valkyrja\Http\Files
     */
    protected $files;

    /**
     * Cookies ($_COOKIE).
     *
     * @var \Valkyrja\Support\Collection
     */
    protected $cookies;

    /**
     * Headers (taken from the $_SERVER).
     *
     * @var \Valkyrja\Http\Headers
     */
    protected $headers;

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

    /**
     * @var array
     */
    protected $languages;

    /**
     * @var array
     */
    protected $charsets;

    /**
     * @var array
     */
    protected $encodings;

    /**
     * @var array
     */
    protected $acceptableContentTypes;

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

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

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

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

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

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

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

    /**
     * @var string
     */
    protected $defaultLocale = 'en';

    /**
     * @var array
     */
    protected static $formats;

    /**
     * Request Constructor.
     *
     * @param array           $query      The GET parameters
     * @param array           $request    The POST parameters
     * @param array           $attributes The request attributes (parameters
     *                                    parsed from the PATH_INFO, ...)
     * @param array           $cookies    The COOKIE parameters
     * @param array           $files      The FILES parameters
     * @param array           $server     The SERVER parameters
     * @param string|resource $content    The raw body data
     */
    public function __construct(
        array $query = [],
        array $request = [],
        array $attributes = [],
        array $cookies = [],
        array $files = [],
        array $server = [],
        $content = null
    ) {
        $this
            ->setQuery($query)
            ->setRequest($request)
            ->setAttributes($attributes)
            ->setCookies($cookies)
            ->setFiles($files)
            ->setServer($server)
            ->setHeaders()
            ->setContent($content)
            ->setLanguages()
            ->setCharsets()
            ->setEncodings()
            ->setAcceptableContentTypes();

        $this->path       = null;
        $this->requestUri = null;
        $this->baseUrl    = null;
        $this->basePath   = null;
        $this->method     = null;
        $this->format     = null;
    }

    /**
     * Create a new Request instance.
     *
     * @param array           $query      The GET parameters
     * @param array           $request    The POST parameters
     * @param array           $attributes The request attributes (parameters
     *                                    parsed from the PATH_INFO, ...)
     * @param array           $cookies    The COOKIE parameters
     * @param array           $files      The FILES parameters
     * @param array           $server     The SERVER parameters
     * @param string|resource $content    The raw body data
     *
     * @return \Valkyrja\Http\Request
     */
    public static function factory(
        array $query = [],
        array $request = [],
        array $attributes = [],
        array $cookies = [],
        array $files = [],
        array $server = [],
        $content = null
    ): Request {
        return new static(
            $query,
            $request,
            $attributes,
            $cookies,
            $files,
            $server,
            $content
        );
    }

    /**
     * Creates a new request with values from PHP super globals.
     *
     * @return \Valkyrja\Http\Request
     */
    public static function createFromGlobals(): Request
    {
        // Create a new request from the PHP globals
        /** @var Request $request */
        $request = new static($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

        if (
            0 === strpos(
                $request->headers()->get('Content-Type'),
                'application/x-www-form-urlencoded'
            )
            &&
            \in_array(
                strtoupper(
                    $request->server()->get(
                        'REQUEST_METHOD',
                        RequestMethod::GET
                    )
                ),
                [
                    RequestMethod::PUT,
                    RequestMethod::DELETE,
                    RequestMethod::PATCH,
                ],
                true
            )
        ) {
            parse_str($request->getContent(), $data);
            $request->request = new Collection($data);
        }

        return $request;
    }

    /**
     * Creates a Request based on a given URI and configuration.
     *
     * The information contained in the URI always take precedence
     * over the other information (server and parameters).
     *
     * @param string $uri        The URI
     * @param string $method     The HTTP method
     * @param array  $parameters The query (GET) or request (POST) parameters
     * @param array  $cookies    The request cookies ($_COOKIE)
     * @param array  $files      The request files ($_FILES)
     * @param array  $server     The server parameters ($_SERVER)
     * @param string $content    The raw body data
     *
     * @return \Valkyrja\Http\Request
     */
    public static function create(
        string $uri,
        string $method = RequestMethod::GET,
        array $parameters = [],
        array $cookies = [],
        array $files = [],
        array $server = [],
        string $content = null
    ): Request {
        $server = array_replace(
            [
                'SERVER_NAME'          => 'localhost',
                'SERVER_PORT'          => 80,
                'HTTP_HOST'            => 'localhost',
                'HTTP_USER_AGENT'      => config()['app']['version'],
                'HTTP_ACCEPT'          => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
                'HTTP_ACCEPT_CHARSET'  => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
                'REMOTE_ADDR'          => '127.0.0.1',
                'SCRIPT_NAME'          => '',
                'SCRIPT_FILENAME'      => '',
                'SERVER_PROTOCOL'      => 'HTTP/1.1',
                'REQUEST_TIME'         => time(),
            ],
            $server
        );

        $server['PATH_INFO']      = '';
        $server['REQUEST_METHOD'] = strtoupper($method);
        $components               = parse_url($uri);
        $request                  = [];
        $query                    = [];

        if (isset($components['host'])) {
            $server['SERVER_NAME'] = $components['host'];
            $server['HTTP_HOST']   = $components['host'];
        }

        if (isset($components['scheme'])) {
            if ('https' === $components['scheme']) {
                $server['HTTPS']       = 'on';
                $server['SERVER_PORT'] = 443;
            } else {
                unset($server['HTTPS']);
                $server['SERVER_PORT'] = 80;
            }
        }

        if (isset($components['port'])) {
            $server['SERVER_PORT'] = $components['port'];
            $server['HTTP_HOST']   = $server['HTTP_HOST']
                . ':'
                . $components['port'];
        }

        if (isset($components['user'])) {
            $server['PHP_AUTH_USER'] = $components['user'];
        }

        if (isset($components['pass'])) {
            $server['PHP_AUTH_PW'] = $components['pass'];
        }

        if (! isset($components['path'])) {
            $components['path'] = '/';
        }

        switch (strtoupper($method)) {
            case RequestMethod::POST:
            case RequestMethod::PUT:
            case RequestMethod::DELETE:
                if (! isset($server['CONTENT_TYPE'])) {
                    $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
                }

                $query = $parameters;

                break;
            case RequestMethod::PATCH:
                $request = $parameters;
                break;
            default:
                $query = $parameters;
                break;
        }

        $queryString = '';

        if (isset($components['query'])) {
            parse_str(html_entity_decode($components['query']), $qs);

            if ($query) {
                $query       = array_replace($qs, $query);
                $queryString = http_build_query($query, '', '&');
            } else {
                $query       = $qs;
                $queryString = $components['query'];
            }
        } elseif ($query) {
            $queryString = http_build_query($query, '', '&');
        }

        $server['REQUEST_URI']  = $components['path']
            . ('' !== $queryString ? '?' . $queryString : '');
        $server['QUERY_STRING'] = $queryString;

        return self::factory(
            $query,
            $request,
            [],
            $cookies,
            $files,
            $server,
            $content
        );
    }

    /**
     * Clones the current request.
     *
     * Note that the session is not cloned as duplicated requests
     * are most of the time sub-requests of the main one.
     */
    public function __clone()
    {
        $this->query      = clone $this->query;
        $this->request    = clone $this->request;
        $this->attributes = clone $this->attributes;
        $this->cookies    = clone $this->cookies;
        $this->files      = clone $this->files;
        $this->server     = clone $this->server;
        $this->headers    = clone $this->headers;
    }

    /**
     * Returns the request as a string.
     *
     * @return string The request
     */
    public function __toString(): string
    {
        return
            sprintf(
                '%s %s %s',
                $this->getMethod(),
                $this->getRequestUri(),
                $this->server->get('SERVER_PROTOCOL')
            )
            . "\r\n"
            . $this->headers
            . "\r\n"
            . $this->getContent();
    }

    /**
     * Return the GET Collection.
     *
     * @return \Valkyrja\Support\Collection
     */
    public function query(): Collection
    {
        if (! $this->query) {
            $this->setQuery();
        }

        return $this->query;
    }

    /**
     * Set the GET parameters.
     *
     * @param array $query
     *
     * @return \Valkyrja\Http\Request
     */
    public function setQuery(array $query = []): Request
    {
        $this->query = new Query($query);

        return $this;
    }

    /**
     * Return the POST Collection.
     *
     * @return \Valkyrja\Support\Collection
     */
    public function request(): Collection
    {
        if (! $this->request) {
            $this->setRequest();
        }

        return $this->request;
    }

    /**
     * Set the POST parameters.
     *
     * @param array $request
     *
     * @return \Valkyrja\Http\Request
     */
    public function setRequest(array $request = []): Request
    {
        $this->request = new Collection($request);

        return $this;
    }

    /**
     * Return the attributes Collection.
     *
     * @return \Valkyrja\Support\Collection
     */
    public function attributes(): Collection
    {
        if (! $this->attributes) {
            $this->setAttributes();
        }

        return $this->attributes;
    }

    /**
     * Set the attributes.
     *
     * @param array $attributes
     *
     * @return \Valkyrja\Http\Request
     */
    public function setAttributes(array $attributes = []): Request
    {
        $this->attributes = new Collection($attributes);

        return $this;
    }

    /**
     * Return the COOKIES Collection.
     *
     * @return \Valkyrja\Support\Collection
     */
    public function cookies(): Collection
    {
        if (! $this->cookies) {
            $this->setCookies();
        }

        return $this->cookies;
    }

    /**
     * Set the COOKIES parameters.
     *
     * @param array $cookies
     *
     * @return \Valkyrja\Http\Request
     */
    public function setCookies(array $cookies = []): Request
    {
        $this->cookies = new Collection($cookies);

        return $this;
    }

    /**
     * Return the FILES Collection.
     *
     * @return \Valkyrja\Http\Files
     */
    public function files(): Files
    {
        if (! $this->files) {
            $this->setFiles();
        }

        return $this->files;
    }

    /**
     * Set the FILES parameters.
     *
     * @param array $files
     *
     * @return \Valkyrja\Http\Request
     */
    public function setFiles(array $files = []): Request
    {
        $this->files = new Files($files);

        return $this;
    }

    /**
     * Return the SERVER Collection.
     *
     * @return \Valkyrja\Http\Server
     */
    public function server(): Server
    {
        if (! $this->server) {
            $this->setServer();
        }

        return $this->server;
    }

    /**
     * Set the SERVER parameters.
     *
     * @param array $server
     *
     * @return \Valkyrja\Http\Request
     */
    public function setServer(array $server = []): Request
    {
        $server = $server
            ?: $_SERVER;

        $this->server = new Server($server);

        return $this;
    }

    /**
     * Return the headers Collection.
     *
     * @return \Valkyrja\Http\Headers
     */
    public function headers(): Headers
    {
        if (! $this->headers) {
            $this->setHeaders();
        }

        return $this->headers;
    }

    /**
     * Set the headers parameters.
     *
     * @param array $headers
     *
     * @return \Valkyrja\Http\Request
     */
    public function setHeaders(array $headers = []): Request
    {
        $headers = $headers ?: $this->server()->getHeaders();

        $this->headers = new Headers($headers);

        return $this;
    }

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

    /**
     * Set the content.
     *
     * @param string $content
     *
     * @return \Valkyrja\Http\Request
     */
    public function setContent(string $content = null): Request
    {
        $this->content = $content;

        return $this;
    }

    /**
     * Get the languages.
     *
     * @return array
     */
    public function getLanguages(): array
    {
        return $this->languages;
    }

    /**
     * Set the languages.
     *
     * @param array $languages
     *
     * @return \Valkyrja\Http\Request
     */
    public function setLanguages(array $languages = []): Request
    {
        $this->languages = $languages;

        return $this;
    }

    /**
     * Get the charsets.
     *
     * @return array
     */
    public function getCharsets(): array
    {
        return $this->charsets;
    }

    /**
     * Set the charsets.
     *
     * @param array $charsets
     *
     * @return \Valkyrja\Http\Request
     */
    public function setCharsets(array $charsets = []): Request
    {
        $this->charsets = $charsets;

        return $this;
    }

    /**
     * Get the encodings.
     *
     * @return array
     */
    public function getEncodings(): array
    {
        return $this->encodings;
    }

    /**
     * Set the encodings.
     *
     * @param array $encodings
     *
     * @return \Valkyrja\Http\Request
     */
    public function setEncodings(array $encodings = []): Request
    {
        $this->encodings = $encodings;

        return $this;
    }

    /**
     * Get the acceptable content types.
     *
     * @return array
     */
    public function getAcceptableContentTypes(): array
    {
        return $this->acceptableContentTypes;
    }

    /**
     * Set the acceptable content types.
     *
     * @param array $acceptableContentTypes
     *
     * @return \Valkyrja\Http\Request
     */
    public function setAcceptableContentTypes(array $acceptableContentTypes = []): Request
    {
        $this->acceptableContentTypes = $acceptableContentTypes;

        return $this;
    }

    /**
     * Gets a "parameter" value from any bag.
     *
     * @param string $key     the key
     * @param mixed  $default the default value if the parameter key does not
     *                        exist
     *
     * @return mixed
     */
    public function get(string $key, $default = null) // : mixed
    {
        if ($this !== $result = $this->attributes->get($key, $this)) {
            return $result;
        }

        if ($this !== $result = $this->query->get($key, $this)) {
            return $result;
        }

        if ($this !== $result = $this->request->get($key, $this)) {
            return $result;
        }

        return $default;
    }

    /**
     * Returns current script name.
     *
     * @return string
     */
    public function getScriptName(): string
    {
        return $this->server->get(
            'SCRIPT_NAME',
            $this->server->get('ORIG_SCRIPT_NAME', '')
        );
    }

    /**
     * Returns the path being requested relative to the executed script.
     *
     * @return string
     */
    public function getPath(): string
    {
        if (null === $this->path) {
            $this->path = $this->server()->get('REQUEST_URI');
        }

        return $this->path;
    }

    /**
     * Returns the path being requested with no query string.
     *
     * @return string
     */
    public function getPathOnly(): string
    {
        $requestUri = $this->getPath();

        // Determine if the request uri has any query parameters
        if (false !== $queryPosition = strpos($requestUri, '?')) {
            // If so get the substring of the uri from start until the query param position
            $requestUri = substr($requestUri, 0, $queryPosition);
        }

        return $requestUri;
    }

    /**
     * Gets the request's scheme.
     *
     * @return string
     */
    public function getScheme(): string
    {
        return $this->isSecure()
            ? 'https'
            : 'http';
    }

    /**
     * Returns the port on which the request is made.
     *
     * This method can read the client port from the "X-Forwarded-Port" header
     * when trusted proxies were set via "setTrustedProxies()".
     *
     * The "X-Forwarded-Port" header must contain the client port.
     *
     * If your reverse proxy uses a different header name than
     * "X-Forwarded-Port", configure it via "setTrustedHeaderName()" with the
     * "client-port" key.
     *
     * @return int
     */
    public function getPort(): int
    {
        return (int) $this->server->get('SERVER_PORT');
    }

    /**
     * Returns the user.
     *
     * @return string
     */
    public function getUser(): string
    {
        return $this->headers->get('PHP_AUTH_USER');
    }

    /**
     * Returns the password.
     *
     * @return string
     */
    public function getPassword(): string
    {
        return $this->headers->get('PHP_AUTH_PW');
    }

    /**
     * Gets the user info.
     *
     * @return string
     */
    public function getUserInfo(): string
    {
        $userinfo = $this->getUser();
        $pass     = $this->getPassword();

        if ($pass) {
            $userinfo .= ':' . $pass;
        }

        return $userinfo;
    }

    /**
     * Returns the HTTP host being requested.
     *
     * The port name will be appended to the host if it's non-standard.
     *
     * @return string
     */
    public function getHttpHost(): string
    {
        $scheme = $this->getScheme();
        $port   = $this->getPort();

        if (
            ('http' === $scheme && $port === 80)
            || ('https' === $scheme && $port === 443)
        ) {
            return $this->getHost();
        }

        return $this->getHost() . ':' . $port;
    }

    /**
     * Returns the requested URI (path and query string).
     *
     * @return string
     */
    public function getRequestUri(): string
    {
        if (null === $this->requestUri) {
            $this->requestUri = $this->getPath() . $this->query;
        }

        return $this->requestUri;
    }

    /**
     * Gets the scheme and HTTP host.
     *
     * @return string
     */
    public function getSchemeAndHttpHost(): string
    {
        return $this->getScheme() . '://' . $this->getHttpHost();
    }

    /**
     * Checks whether the request is secure or not.
     *
     * This method can read the client protocol from the "X-Forwarded-Proto"
     * header when trusted proxies were set via "setTrustedProxies()".
     *
     * The "X-Forwarded-Proto" header must contain the protocol: "https" or
     * "http".
     *
     * If your reverse proxy uses a different header name than
     * "X-Forwarded-Proto"
     * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()"
     * with the "client-proto" key.
     *
     * @return bool
     */
    public function isSecure(): bool
    {
        $https = $this->server->get('HTTPS');

        return $https && 'off' !== strtolower($https);
    }

    /**
     * Returns the host name.
     *
     * @return string
     */
    public function getHost(): string
    {
        return $this->headers->get('Host');
    }

    /**
     * Sets the request method.
     *
     * @param string $method
     *
     * @return \Valkyrja\Http\Request
     */
    public function setMethod(string $method): Request
    {
        $this->method = null;
        $this->server->set('REQUEST_METHOD', $method);

        return $this;
    }

    /**
     * Gets the request "intended" method.
     *
     * @return string The request method
     *
     * @see getRealMethod()
     */
    public function getMethod(): string
    {
        if (null === $this->method) {
            $this->method = strtoupper(
                $this->server->get('REQUEST_METHOD', RequestMethod::GET)
            );

            if (RequestMethod::POST === $this->method) {
                if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
                    $this->method = strtoupper($method);
                } elseif (self::$httpMethodParameterOverride) {
                    $this->method = strtoupper(
                        $this->request->get(
                            '_method',
                            $this->query->get(
                                '_method',
                                RequestMethod::POST
                            )
                        )
                    );
                }
            }
        }

        return $this->method;
    }

    /**
     * Gets the "real" request method.
     *
     * @return string The request method
     *
     * @see getMethod()
     */
    public function getRealMethod(): string
    {
        return strtoupper(
            $this->server->get('REQUEST_METHOD', RequestMethod::GET)
        );
    }

    /**
     * Gets the mime type associated with the format.
     *
     * @param string $format The format
     *
     * @return string
     */
    public function getMimeType(string $format): string
    {
        return isset(static::$formats[$format])
            ? static::$formats[$format][0]
            : null;
    }

    /**
     * Gets the mime types associated with the format.
     *
     * @param string $format The format
     *
     * @return array
     */
    public static function getMimeTypes(string $format): array
    {
        return static::$formats[$format] ?? [];
    }

    /**
     * Gets the format associated with the mime type.
     *
     * @param string $mimeType The associated mime type
     *
     * @return string
     */
    public function getFormat(string $mimeType): string
    {
        $canonicalMimeType = null;

        if (false !== $pos = strpos($mimeType, ';')) {
            $canonicalMimeType = substr($mimeType, 0, $pos);
        }

        foreach (static::FORMATS as $format => $mimeTypes) {
            if (\in_array($mimeType, $mimeTypes, true)) {
                return $format;
            }

            if (
                null !== $canonicalMimeType
                && \in_array(
                    $canonicalMimeType,
                    $mimeTypes,
                    true
                )
            ) {
                return $format;
            }
        }

        return 'html';
    }

    /**
     * Gets the request format.
     *
     * @param string $default The default format
     *
     * @return string
     */
    public function getRequestFormat(string $default = 'html'): string
    {
        if (null === $this->format) {
            $this->format = $this->attributes->get('_format', $default);
        }

        return $this->format;
    }

    /**
     * Sets the request format.
     *
     * @param string $format The request format
     *
     * @return \Valkyrja\Http\Request
     */
    public function setRequestFormat(string $format): Request
    {
        $this->format = $format;

        return $this;
    }

    /**
     * Gets the format associated with the request.
     *
     * @return string
     */
    public function getContentType(): string
    {
        return $this->getFormat($this->headers->get('Content-Type'));
    }

    /**
     * Get the locale.
     *
     * @return string
     */
    public function getLocale(): string
    {
        return $this->locale ?? $this->defaultLocale;
    }

    /**
     * Checks if the request method is of specified type.
     *
     * @param string $method Uppercase request method (GET, POST etc)
     *
     * @return bool
     */
    public function isMethod(string $method): bool
    {
        return $this->getMethod() === strtoupper($method);
    }

    /**
     * Gets the Etags.
     *
     * @return array
     */
    public function getETags(): array
    {
        return preg_split(
            '/\s*,\s*/',
            $this->headers->get('If-None-Match'),
            null,
            PREG_SPLIT_NO_EMPTY
        );
    }

    /**
     * @return bool
     */
    public function isNoCache(): bool
    {
        return 'no-cache' === $this->headers->get('Pragma');
    }

    /**
     * Is this an AJAX request?
     *
     * @return bool
     */
    public function isXmlHttpRequest(): bool
    {
        return 'XMLHttpRequest' === $this->headers->get('X-Requested-With');
    }

    /**
     * The items provided by this provider.
     *
     * @return array
     */
    public static function provides(): array
    {
        return [
            Request::class,
        ];
    }

    /**
     * Publish the provider.
     *
     * @param Application $app The application
     *
     * @return void
     */
    public static function publish(Application $app): void
    {
        $app->container()->singleton(
            Request::class,
            static::createFromGlobals()
        );
    }
}


<?php

/**
* Jin Framework
* Diatem
*/

namespace Jin2\Form\Components;

/**
 * Composant <select>
*/
class Select extends AbstractComponent
{

  /**
  * @var array
  */
  protected $values;

  /**
   * @var array Attributs ajoutés sur select
   */
  protected $selectAttributes = array();

  /**
   * Implémente la fonction AbstractComponent::getType()
   *
   * @return string
   */
  public static function getType()
  {
    return 'select';
  }

  /**
   * Rendu du composant
   *
   * @return string
   */
  public function render()
  {
    $optionHtml = static::getAssetContent('option');

    $addContent = '';
    foreach ($this->values as $k => $option) {
      $ac = $optionHtml;
      $attributes = '';
      if (count($option['attributes']) > 0) {
        foreach ($option['attributes'] as $att_k => $att_v) {
          $attributes .= srintf(' %s="%s"', $att_k, $att_v);
        }
      }
      $ac = str_replace('%value%', $option['value'], $ac);
      $ac = str_replace('%attributes%', $attributes, $ac);
      $ac = str_replace('%label%', $k, $ac);

      $selected = false;
      if ($this->getValue() == $option['value']) {
        $selected = true;
      }

      $ac = str_replace('%selected%', ($selected ? 'selected="selected"' : ''), $ac);
      $addContent .= $ac;
    }

    $html = parent::render();
    $strAttributes = '';
    foreach ($this->selectAttributes as $key => $value) {
      $strAttributes .= ' ' . $key . '="' . $value . '"';
    }
    $html = str_replace('%selectAttributes%', $strAttributes, $html);
    $html = str_replace('%items%', $addContent, $html);

    return $html;
  }

  /**
   * Ajoute un choix dans la liste
   *
   * @param string $value      Valeur du choix
   * @param string $label      Label affiché
   * @param array  $attributes Attributs supplémentaires pour l'option
   */
  public function addValue($value, $label, $attributes = array())
  {
    $this->values[$label] = array(
      'value'      => $value,
      'attributes' => $attributes
    );
  }

  /**
   * Définit les valeurs du Select
   *
   * @param array $values Tableau de valeurs (clé/valeur) ex. array('label'=>'val','label','val');
   */
  public function setValues($values)
  {
    if (count($values) > 0) {
      foreach ($values as $label => $value) {
        $this->addValue($value, $label);
      }
    }
  }

  /**
   * Définit une datasource permettant de définir des valeurs au select
   *
   * @param type $queryResult
   * @param type $colNameForLabel
   * @param type $colNameForValue
   */
  public function setDataSource($queryResult, $colNameForLabel, $colNameForValue)
  {
    foreach($queryResult as $value) {
      $this->values[$value[$colNameForLabel]] = array(
        'value'      => $value[$colNameForValue],
        'attributes' => array()
      );
    }
  }

  /**
   * Ajoute un nouvel attribut sur le select
   *
   * @param  string $attributeName   Nom de l'attribut
   * @param  string $attributeValue  Value de l'attribut
   * @return boolean                 Retourne FALSE si cet atribut était déjà ajouté
   */
  public function addSelectAttribute($attributeName, $attributeValue)
  {
    if (!array_key_exists($attributeName, $this->selectAttributes)) {
      $this->selectAttributes[$attributeName] = $attributeValue;
      return true;
    }
    return false;
  }

  /**
   * Supprime un attribut ajouté sur le select
   *
   * @param  string $attributeName  Nom de l'attribut
   * @return boolean                Retourne FALSE si cet attribut n'était pas ajouté
   */
  public function removeSelectAttribute($attributeName)
  {
    if (isset($this->selectAttributes[$attributeName])) {
      unset($this->selectAttributes[$attributeName]);
      return true;
    }
    return false;
  }

  /**
   * Retourne un tableau des attributs ajoutés
   *
   * @return array
   */
  public function getSelectAttributes()
  {
    return $this->selectAttributes;
  }

}


<?php

namespace Reaction;

use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\LoopInterface;
use React\Http\Server as Http;
use React\Socket\ServerInterface as SocketServerInterface;
use React\Socket\Server as Socket;
use Reaction;
use Reaction\Base\Logger\Debugger;
use Reaction\Db\DatabaseInterface;
use Reaction\DI\ServiceLocator;
use Reaction\DI\ServiceLocatorAutoloadInterface;
use Reaction\Exceptions\InvalidArgumentException;
use Reaction\Helpers\Console;
use Reaction\I18n\I18N;
use Reaction\Promise\ExtendedPromiseInterface;

/**
 * Class StaticApplicationAbstract
 * @package Reaction
 */
abstract class StaticApplicationAbstract extends ServiceLocator implements StaticApplicationInterface, ServiceLocatorAutoloadInterface
{
    /**
     * @var LoopInterface Event loop
     */
    public $loop;
    /**
     * @var bool Flag that event loop is external and there is no need start it again
     */
    public $loopIsExternal = false;
    /**
     * @var Http HTTP server
     */
    public $http;
    /**
     * @var Socket Socket server
     */
    public $socket;

    /** @var string Environment type */
    public $envType = self::APP_ENV_DEV;
    /** @var bool Debug flag */
    public $debug = false;
    /** @var bool Testing flag */
    public $test = false;
    /** @var string Default charset */
    public $charset = 'UTF-8';
    /** @var string Default language */
    public $language = 'en-US';
    /** @var string Source language */
    public $sourceLanguage = 'en-US';
    /** @var string Default timezone */
    public $timeZone = 'UTC';
    /** @var string */
    public $hostname = 'localhost';
    /** @var integer */
    public $port = 4000;

    /** @var ExtendedPromiseInterface */
    public $initPromise;
    /** @var bool */
    public $initialized = false;

    /** @var array Added middleware */
    protected $middleware = [];
    /** @var array Aliases defined */
    protected $aliases = [];

    /**
     * @inheritdoc
     */
    public function init()
    {
        if (Reaction::isDev()) {
            error_reporting(E_ALL);
        }
        set_error_handler([$this, 'handleError']);
        $this->loop = Reaction::create(\React\EventLoop\LoopInterface::class);
    }

    /**
     * Initialize router
     */
    public function initRouter()
    {
        $this->router->initRoutes();
        $this->addMiddleware([$this, 'processRequest']);
    }

    /**
     * Initialize with React HTTP and Socket servers
     * Used in web application
     */
    public function initHttp()
    {
        $this->socket = Reaction::create(SocketServerInterface::class);
        $this->http = Reaction::create(Http::class, [$this->middleware]);
        $this->http->listen($this->socket);
        //Exception handler
        $this->http->on('error', function(\Throwable $error) {
            $this->logger->alert($error);
        });
    }

    /**
     * Run application
     */
    public function run()
    {
        $isConsole = Reaction::isConsoleApp();
        if (!$isConsole && isset($this->http)) {
            echo sprintf("Running server on 'http://%s:%d'\n", $this->hostname, $this->port);
        }
        $this->initPromise = $this->loadComponents()->always(
            function() use ($isConsole) {
                if (!$isConsole) {
                    Reaction::info('StaticApplication initialized');
                }
                $this->initialized = true;
                return true;
            }
        );
        //Kickstart loop if it is not external
        if (!$this->loopIsExternal) {
            $this->loop->run();
        }
    }

    /**
     * Add middleware to application
     * @param callable|array $middleware
     */
    public function addMiddleware($middleware) {
        if (!is_callable($middleware) && !is_array($middleware)) {
            throw new InvalidArgumentException("Middleware must be a valid callable");
        } else {
            $this->middleware[] = $middleware;
        }
    }

    /**
     * Process React request
     * @param ServerRequestInterface $request
     * @return ExtendedPromiseInterface
     */
    public function processRequest(ServerRequestInterface $request)
    {
        /** @var RequestApplicationInterface $app */
        $app = $this->createRequestApplication($request);
        return $app->handleRequest();
    }

    /**
     * Create RequestApplicationInterface instance from react request
     * @param ServerRequestInterface $request
     * @return RequestApplicationInterface
     */
    public function createRequestApplication(ServerRequestInterface $request)
    {
        $config = Reaction::$config->get('appRequest');
        $config = ['request' => $request] + $config;
        $app = Reaction::create($config);
        return $app;
    }

    /**
     * Translates a path alias into an actual path.
     *
     * The translation is done according to the following procedure:
     *
     * 1. If the given alias does not start with '@', it is returned back without change;
     * 2. Otherwise, look for the longest registered alias that matches the beginning part
     *    of the given alias. If it exists, replace the matching part of the given alias with
     *    the corresponding registered path.
     * 3. Throw an exception or return false, depending on the `$throwException` parameter.
     *
     * For example, by default '@reaction' is registered as the alias to the Reaction framework directory,
     * say '/path/to/reaction'. The alias '@reaction/Web' would then be translated into '/path/to/reaction/Web'.
     *
     * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config'
     * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path.
     * This is because the longest alias takes precedence.
     *
     * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced
     * instead of '@foo/bar', because '/' serves as the boundary character.
     *
     * Note, this method does not check if the returned path exists or not.
     *
     * See the [guide article on aliases](guide:concept-aliases) for more information.
     *
     * @param string $alias the alias to be translated.
     * @param bool $throwException whether to throw an exception if the given alias is invalid.
     * If this is false and an invalid alias is given, false will be returned by this method.
     * @return string|bool the path corresponding to the alias, false if the root alias is not previously registered.
     * @throws InvalidArgumentException if the alias is invalid while $throwException is true.
     * @see setAlias()
     */
    public function getAlias($alias, $throwException = true)
    {
        if (strncmp($alias, '@', 1)) {
            // not an alias
            return $alias;
        }

        $pos = strpos($alias, '/');
        $root = $pos === false ? $alias : substr($alias, 0, $pos);

        if (isset($this->aliases[$root])) {
            if (is_string($this->aliases[$root])) {
                $aliasResolved = $this->aliases[$root];
                if (strncmp($this->aliases[$root], '@', 1) === 0) {
                    $aliasResolved = $this->getAlias($aliasResolved);
                }
                return $pos === false ? $aliasResolved : $aliasResolved . substr($alias, $pos);
            }

            foreach ($this->aliases[$root] as $name => $path) {
                if (strpos($alias . '/', $name . '/') === 0) {
                    return $path . substr($alias, strlen($name));
                }
            }
        }

        if ($throwException) {
            throw new InvalidArgumentException("Invalid path alias: $alias");
        }

        return false;
    }

    /**
     * Returns the root alias part of a given alias.
     * A root alias is an alias that has been registered via [[setAlias()]] previously.
     * If a given alias matches multiple root aliases, the longest one will be returned.
     * @param string $alias the alias
     * @return string|bool the root alias, or false if no root alias is found
     */
    public function getRootAlias($alias)
    {
        $pos = strpos($alias, '/');
        $root = $pos === false ? $alias : substr($alias, 0, $pos);

        if (isset($this->aliases[$root])) {
            if (is_string($this->aliases[$root])) {
                return $root;
            }

            foreach ($this->aliases[$root] as $name => $path) {
                if (strpos($alias . '/', $name . '/') === 0) {
                    return $name;
                }
            }
        }

        return false;
    }

    /**
     * Registers a path alias.
     *
     * A path alias is a short name representing a long path (a file path, a URL, etc.)
     * For example, we use '@reaction' as the alias of the path to the Reaction framework directory.
     *
     * A path alias must start with the character '@' so that it can be easily differentiated
     * from non-alias paths.
     *
     * Note that this method does not check if the given path exists or not. All it does is
     * to associate the alias with the path.
     *
     * Any trailing '/' and '\' characters in the given path will be trimmed.
     *
     * See the [guide article on aliases](guide:concept-aliases) for more information.
     *
     * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character.
     * It may contain the forward slash '/' which serves as boundary character when performing
     * alias translation by [[getAlias()]].
     * @param string $path the path corresponding to the alias. If this is null, the alias will
     * be removed. Trailing '/' and '\' characters will be trimmed. This can be
     *
     * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`)
     * - a URL (e.g. `http://www.example.com`)
     * - a path alias (e.g. `@root/config`). In this case, the path alias will be converted into the
     *   actual path first by calling [[getAlias()]].
     *
     * @see getAlias()
     */
    public function setAlias($alias, $path)
    {
        if (strncmp($alias, '@', 1)) {
            $alias = '@' . $alias;
        }
        $pos = strpos($alias, '/');
        $root = $pos === false ? $alias : substr($alias, 0, $pos);
        if ($path !== null) {
            $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : $this->getAlias($path);
            if (!isset($this->aliases[$root])) {
                if ($pos === false) {
                    $this->aliases[$root] = $path;
                } else {
                    $this->aliases[$root] = [$alias => $path];
                }
            } elseif (is_string($this->aliases[$root])) {
                if ($pos === false) {
                    $this->aliases[$root] = $path;
                } else {
                    $this->aliases[$root] = [
                        $alias => $path,
                        $root => $this->aliases[$root],
                    ];
                }
            } else {
                $this->aliases[$root][$alias] = $path;
                krsort($this->aliases[$root]);
            }
        } elseif (isset($this->aliases[$root])) {
            if (is_array($this->aliases[$root])) {
                unset($this->aliases[$root][$alias]);
            } elseif ($pos === false) {
                unset($this->aliases[$root]);
            }
        }
    }

    /**
     * Set aliases from config
     * @param array $aliases
     */
    public function setAliases($aliases)
    {
        foreach ($aliases as $alias => $path) {
            $this->setAlias($alias, $path);
        }
    }

    /**
     * Get path for '@views'
     * @return string
     */
    public function getViewPath()
    {
        return $this->getAlias('@views');
    }

    /**
     * Get path for '@runtime'
     * @return string
     */
    public function getRuntimePath()
    {
        return $this->getAlias('@runtime');
    }

    /**
     * Returns the root directory of the component.
     * It defaults to the directory containing the component class file.
     * @return string the root directory of the component.
     */
    public function getBasePath()
    {
        return parent::getBasePath();
    }

    /**
     * Get Application auth manager
     * @return \Reaction\Rbac\ManagerInterface|null
     * @throws Exceptions\InvalidConfigException
     */
    public function getAuthManager() {
        /** @var \Reaction\Rbac\ManagerInterface|null $component */
        $component = $this->has('authManager') ? $this->get('authManager') : null;
        return $component;
    }

    /**
     * Get default database
     * @return DatabaseInterface
     * @throws Exceptions\InvalidConfigException
     */
    public function getDb() {
        /** @var DatabaseInterface $db */
        $db = $this->get('db');
        return $db;
    }

    /**
     * Get internationalization component
     * @return I18N
     * @throws Exceptions\InvalidConfigException
     */
    public function getI18n()
    {
        /** @var I18N $i18n */
        $i18n = $this->get('i18n');
        return $i18n;
    }

    /**
     * Handles PHP execution errors such as warnings and notices.
     *
     * @param int $code the level of the error raised.
     * @param string $message the error message.
     * @param string $file the filename that the error was raised in.
     * @param int $line the line number the error was raised at.
     */
    public function handleError($code, $message, $file, $line)
    {
        if (error_reporting() & $code) {
            /** @var Reaction\Base\Logger\StdioLogger $logger */
            $logger = Reaction::$app->logger;
            $reportMessage = $message;
            if (!$logger->withLineNum) {
                $reportMessage .= "\n" . $file;
                $reportMessage .= ":" . $line;
            }
            list($level, $format) = $this->getErrorLogLevel($code);
            $format = is_array($format) ? $format : [$format];
            $reportMessage = Console::ansiFormat('[' . strtoupper($level) . '] ' . $reportMessage, $format);
            if (Reaction::isDebug()) {
                $trace = Debugger::reduceBacktrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true);
                $num = count($trace);
                foreach ($trace as $row) {
                    $reportMessage .= "\n $num - " . $row['file'] . ":" . $row['line'];
                    --$num;
                }
            }
            $logger->logRaw($reportMessage, [], 1);
        }
    }

    /**
     * Get error log level by error code
     * @param int $code
     * @return array|mixed
     */
    protected function getErrorLogLevel($code)
    {
        $levels = [
            E_ERROR => ['error', [Console::FG_RED, Console::BOLD]],
            E_WARNING => ['warning', [Console::FG_YELLOW, Console::BOLD]],
            E_PARSE => ['parse error', Console::FG_RED],
            E_NOTICE => ['notice', Console::FG_YELLOW],
            E_CORE_ERROR => ['core error', Console::FG_RED],
            E_CORE_WARNING => ['core warning', Console::FG_YELLOW],
            E_STRICT => ['strict warning', Console::FG_BLUE],
        ];
        foreach ($levels as $mask => $level) {
            if ($mask & $code) {
                return $level;
            }
        }
        return ['low error', Console::FG_GREY];
    }
}