<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\console\widgets;

use Yii;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Console;

/**
 * Table class displays a table in console.
 *
 * For example,
 *
 * ```php
 * $table = new Table();
 *
 * echo $table
 *     ->setHeaders(['test1', 'test2', 'test3'])
 *     ->setRows([
 *         ['col1', 'col2', 'col3'],
 *         ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
 *     ])
 *     ->run();
 * ```
 *
 * or
 *
 * ```php
 * echo Table::widget([
 *     'headers' => ['test1', 'test2', 'test3'],
 *     'rows' => [
 *         ['col1', 'col2', 'col3'],
 *         ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
 *     ],
 * ]);
 *
 * @property string $listPrefix List prefix. This property is write-only.
 * @property int $screenWidth Screen width. This property is write-only.
 *
 * @author Daniel Gomez Pan <pana_1990@hotmail.com>
 * @since 2.0.13
 */
class Table extends Widget
{
    const DEFAULT_CONSOLE_SCREEN_WIDTH = 120;
    const CONSOLE_SCROLLBAR_OFFSET = 3;
    const CHAR_TOP = 'top';
    const CHAR_TOP_MID = 'top-mid';
    const CHAR_TOP_LEFT = 'top-left';
    const CHAR_TOP_RIGHT = 'top-right';
    const CHAR_BOTTOM = 'bottom';
    const CHAR_BOTTOM_MID = 'bottom-mid';
    const CHAR_BOTTOM_LEFT = 'bottom-left';
    const CHAR_BOTTOM_RIGHT = 'bottom-right';
    const CHAR_LEFT = 'left';
    const CHAR_LEFT_MID = 'left-mid';
    const CHAR_MID = 'mid';
    const CHAR_MID_MID = 'mid-mid';
    const CHAR_RIGHT = 'right';
    const CHAR_RIGHT_MID = 'right-mid';
    const CHAR_MIDDLE = 'middle';

    /**
     * @var array table headers
     */
    private $_headers = [];
    /**
     * @var array table rows
     */
    private $_rows = [];
    /**
     * @var array table chars
     */
    private $_chars = [
        self::CHAR_TOP => '═',
        self::CHAR_TOP_MID => '╤',
        self::CHAR_TOP_LEFT => '╔',
        self::CHAR_TOP_RIGHT => '╗',
        self::CHAR_BOTTOM => '═',
        self::CHAR_BOTTOM_MID => '╧',
        self::CHAR_BOTTOM_LEFT => '╚',
        self::CHAR_BOTTOM_RIGHT => '╝',
        self::CHAR_LEFT => '║',
        self::CHAR_LEFT_MID => '╟',
        self::CHAR_MID => '─',
        self::CHAR_MID_MID => '┼',
        self::CHAR_RIGHT => '║',
        self::CHAR_RIGHT_MID => '╢',
        self::CHAR_MIDDLE => '│',
    ];
    /**
     * @var array table column widths
     */
    private $_columnWidths = [];
    /**
     * @var int screen width
     */
    private $_screenWidth;
    /**
     * @var string list prefix
     */
    private $_listPrefix = '• ';

    /**
     * Set table headers.
     *
     * @param array $headers table headers
     * @return $this
     */
    public function setHeaders(array $headers)
    {
        $this->_headers = array_values($headers);
        return $this;
    }

    /**
     * Set table rows.
     *
     * @param array $rows table rows
     * @return $this
     */
    public function setRows(array $rows)
    {
        $this->_rows = array_map('array_values', $rows);
        return $this;
    }

    /**
     * Set table chars.
     *
     * @param array $chars table chars
     * @return $this
     */
    public function setChars(array $chars)
    {
        $this->_chars = $chars;
        return $this;
    }

    /**
     * Set screen width.
     *
     * @param int $width screen width
     * @return $this
     */
    public function setScreenWidth($width)
    {
        $this->_screenWidth = $width;
        return $this;
    }

    /**
     * Set list prefix.
     *
     * @param string $listPrefix list prefix
     * @return $this
     */
    public function setListPrefix($listPrefix)
    {
        $this->_listPrefix = $listPrefix;
        return $this;
    }

    /**
     * @return string the rendered table
     */
    public function run()
    {
        $this->calculateRowsSize();
        $headerCount = count($this->_headers);

        $buffer = $this->renderSeparator(
            $this->_chars[self::CHAR_TOP_LEFT],
            $this->_chars[self::CHAR_TOP_MID],
            $this->_chars[self::CHAR_TOP],
            $this->_chars[self::CHAR_TOP_RIGHT]
        );
        // Header
        if ($headerCount > 0) {
            $buffer .= $this->renderRow($this->_headers,
                $this->_chars[self::CHAR_LEFT],
                $this->_chars[self::CHAR_MIDDLE],
                $this->_chars[self::CHAR_RIGHT]
            );
        }

        // Content
        foreach ($this->_rows as $i => $row) {
            if ($i > 0 || $headerCount > 0) {
                $buffer .= $this->renderSeparator(
                    $this->_chars[self::CHAR_LEFT_MID],
                    $this->_chars[self::CHAR_MID_MID],
                    $this->_chars[self::CHAR_MID],
                    $this->_chars[self::CHAR_RIGHT_MID]
                );
            }
            $buffer .= $this->renderRow($row,
                $this->_chars[self::CHAR_LEFT],
                $this->_chars[self::CHAR_MIDDLE],
                $this->_chars[self::CHAR_RIGHT]);
        }

        $buffer .= $this->renderSeparator(
            $this->_chars[self::CHAR_BOTTOM_LEFT],
            $this->_chars[self::CHAR_BOTTOM_MID],
            $this->_chars[self::CHAR_BOTTOM],
            $this->_chars[self::CHAR_BOTTOM_RIGHT]
        );

        return $buffer;
    }

    /**
     * Renders a row of data into a string.
     *
     * @param array $row row of data
     * @param string $spanLeft character for left border
     * @param string $spanMiddle character for middle border
     * @param string $spanRight character for right border
     * @return string
     * @see \yii\console\widgets\Table::render()
     */
    protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight)
    {
        $size = $this->_columnWidths;

        $buffer = '';
        $arrayPointer = [];
        $finalChunk = [];
        for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) {
            $buffer .= $spanLeft . ' ';
            foreach ($size as $index => $cellSize) {
                $cell = isset($row[$index]) ? $row[$index] : null;
                $prefix = '';
                if ($index !== 0) {
                    $buffer .= $spanMiddle . ' ';
                }
                if (is_array($cell)) {
                    if (empty($finalChunk[$index])) {
                        $finalChunk[$index] = '';
                        $start = 0;
                        $prefix = $this->_listPrefix;
                        if (!isset($arrayPointer[$index])) {
                            $arrayPointer[$index] = 0;
                        }
                    } else {
                        $start = mb_strwidth($finalChunk[$index], Yii::$app->charset);
                    }
                    $chunk = mb_substr($cell[$arrayPointer[$index]], $start, $cellSize - 4, Yii::$app->charset);
                    $finalChunk[$index] .= $chunk;
                    if (isset($cell[$arrayPointer[$index] + 1]) && $finalChunk[$index] === $cell[$arrayPointer[$index]]) {
                        $arrayPointer[$index]++;
                        $finalChunk[$index] = '';
                    }
                } else {
                    $chunk = mb_substr($cell, ($cellSize * $i) - ($i * 2), $cellSize - 2, Yii::$app->charset);
                }
                $chunk = $prefix . $chunk;
                $repeat = $cellSize - mb_strwidth($chunk, Yii::$app->charset) - 1;
                $buffer .= $chunk;
                if ($repeat >= 0) {
                    $buffer .= str_repeat(' ', $repeat);
                }
            }
            $buffer .= "$spanRight\n";
        }

        return $buffer;
    }

    /**
     * Renders separator.
     *
     * @param string $spanLeft character for left border
     * @param string $spanMid character for middle border
     * @param string $spanMidMid character for middle-middle border
     * @param string $spanRight character for right border
     * @return string the generated separator row
     * @see \yii\console\widgets\Table::render()
     */
    protected function renderSeparator($spanLeft, $spanMid, $spanMidMid, $spanRight)
    {
        $separator = $spanLeft;
        foreach ($this->_columnWidths as $index => $rowSize) {
            if ($index !== 0) {
                $separator .= $spanMid;
            }
            $separator .= str_repeat($spanMidMid, $rowSize);
        }
        $separator .= $spanRight . "\n";
        return $separator;
    }

    /**
     * Calculate the size of rows to draw anchor of columns in console.
     *
     * @see \yii\console\widgets\Table::render()
     */
    protected function calculateRowsSize()
    {
        $this->_columnWidths = $columns = [];
        $totalWidth = 0;
        $screenWidth = $this->getScreenWidth() - self::CONSOLE_SCROLLBAR_OFFSET;

        $headerCount = count($this->_headers);
        if (empty($this->_rows)) {
            $rowColCount = 0;
        } else {
            $rowColCount = max(array_map('count', $this->_rows));
        }
        $count = max($headerCount, $rowColCount);
        for ($i = 0; $i < $count; $i++) {
            $columns[] = ArrayHelper::getColumn($this->_rows, $i);
            if ($i < $headerCount) {
                $columns[$i][] = $this->_headers[$i];
            }
        }

        foreach ($columns as $column) {
            $columnWidth = max(array_map(function ($val) {
                if (is_array($val)) {
                    $encodings = array_fill(0, count($val), Yii::$app->charset);
                    return max(array_map('mb_strwidth', $val, $encodings)) + mb_strwidth($this->_listPrefix, Yii::$app->charset);
                }

                return mb_strwidth($val, Yii::$app->charset);
            }, $column)) + 2;
            $this->_columnWidths[] = $columnWidth;
            $totalWidth += $columnWidth;
        }

        $relativeWidth = $screenWidth / $totalWidth;

        if ($totalWidth > $screenWidth) {
            foreach ($this->_columnWidths as $j => $width) {
                $this->_columnWidths[$j] = (int) ($width * $relativeWidth);
                if ($j === count($this->_columnWidths)) {
                    $this->_columnWidths = $totalWidth;
                }
                $totalWidth -= $this->_columnWidths[$j];
            }
        }
    }

    /**
     * Calculate the height of a row.
     *
     * @param array $row
     * @return int maximum row per cell
     * @see \yii\console\widgets\Table::render()
     */
    protected function calculateRowHeight($row)
    {
        $rowsPerCell = array_map(function ($size, $columnWidth) {
            if (is_array($columnWidth)) {
                $rows = 0;
                foreach ($columnWidth as $width) {
                    $rows += ceil($width / ($size - 2));
                }

                return $rows;
            }

            return ceil($columnWidth / ($size - 2));
        }, $this->_columnWidths, array_map(function ($val) {
            if (is_array($val)) {
                $encodings = array_fill(0, count($val), Yii::$app->charset);
                return array_map('mb_strwidth', $val, $encodings);
            }

            return mb_strwidth($val, Yii::$app->charset);
        }, $row)
        );

        return max($rowsPerCell);
    }

    /**
     * Getting screen width.
     * If it is not able to determine screen width, default value `123` will be set.
     *
     * @return int screen width
     */
    protected function getScreenWidth()
    {
        if (!$this->_screenWidth) {
            $size = Console::getScreenSize();
            $this->_screenWidth = isset($size[0])
                ? $size[0]
                : self::DEFAULT_CONSOLE_SCREEN_WIDTH + self::CONSOLE_SCROLLBAR_OFFSET;
        }
        return $this->_screenWidth;
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\web;

use Yii;
use yii\base\InlineAction;
use yii\helpers\Url;

/**
 * Controller is the base class of web controllers.
 *
 * For more details and usage information on Controller, see the [guide article on controllers](guide:structure-controllers).
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class Controller extends \yii\base\Controller
{
    /**
     * @var bool whether to enable CSRF validation for the actions in this controller.
     * CSRF validation is enabled only when both this property and [[\yii\web\Request::enableCsrfValidation]] are true.
     */
    public $enableCsrfValidation = true;
    /**
     * @var array the parameters bound to the current action.
     */
    public $actionParams = [];

    /**
     * Renders a view in response to an AJAX request.
     *
     * This method is similar to [[renderPartial()]] except that it will inject into
     * the rendering result with JS/CSS scripts and files which are registered with the view.
     * For this reason, you should use this method instead of [[renderPartial()]] to render
     * a view to respond to an AJAX request.
     *
     * @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
     * @return string the rendering result.
     */
    public function renderAjax($view, $params = [])
    {
        return $this->getView()->renderAjax($view, $params, $this);
    }

    /**
     * Send data formatted as JSON.
     *
     * This method is a shortcut for sending data formatted as JSON. It will return
     * the [[Application::getResponse()|response]] application component after configuring
     * the [[Response::$format|format]] and setting the [[Response::$data|data]] that should
     * be formatted. A common usage will be:
     *
     * ```php
     * return $this->asJson($data);
     * ```
     *
     * @param mixed $data the data that should be formatted.
     * @return Response a response that is configured to send `$data` formatted as JSON.
     * @since 2.0.11
     * @see Response::$format
     * @see Response::FORMAT_JSON
     * @see JsonResponseFormatter
     */
    public function asJson($data)
    {
        $response = Yii::$app->getResponse();
        $response->format = Response::FORMAT_JSON;
        $response->data = $data;
        return $response;
    }

    /**
     * Send data formatted as XML.
     *
     * This method is a shortcut for sending data formatted as XML. It will return
     * the [[Application::getResponse()|response]] application component after configuring
     * the [[Response::$format|format]] and setting the [[Response::$data|data]] that should
     * be formatted. A common usage will be:
     *
     * ```php
     * return $this->asXml($data);
     * ```
     *
     * @param mixed $data the data that should be formatted.
     * @return Response a response that is configured to send `$data` formatted as XML.
     * @since 2.0.11
     * @see Response::$format
     * @see Response::FORMAT_XML
     * @see XmlResponseFormatter
     */
    public function asXml($data)
    {
        $response = Yii::$app->getResponse();
        $response->format = Response::FORMAT_XML;
        $response->data = $data;
        return $response;
    }

    /**
     * Binds the parameters to the action.
     * This method is invoked by [[\yii\base\Action]] when it begins to run with the given parameters.
     * This method will check the parameter names that the action requires and return
     * the provided parameters according to the requirement. If there is any missing parameter,
     * an exception will be thrown.
     * @param \yii\base\Action $action the action to be bound with parameters
     * @param array $params the parameters to be bound to the action
     * @return array the valid parameters that the action can run with.
     * @throws BadRequestHttpException if there are missing or invalid parameters.
     */
    public function bindActionParams($action, $params)
    {
        if ($action instanceof InlineAction) {
            $method = new \ReflectionMethod($this, $action->actionMethod);
        } else {
            $method = new \ReflectionMethod($action, 'run');
        }

        $args = [];
        $missing = [];
        $actionParams = [];
        foreach ($method->getParameters() as $param) {
            $name = $param->getName();
            if (array_key_exists($name, $params)) {
                if ($param->isArray()) {
                    $args[] = $actionParams[$name] = (array) $params[$name];
                } elseif (!is_array($params[$name])) {
                    $args[] = $actionParams[$name] = $params[$name];
                } else {
                    throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [
                        'param' => $name,
                    ]));
                }
                unset($params[$name]);
            } elseif ($param->isDefaultValueAvailable()) {
                $args[] = $actionParams[$name] = $param->getDefaultValue();
            } else {
                $missing[] = $name;
            }
        }

        if (!empty($missing)) {
            throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [
                'params' => implode(', ', $missing),
            ]));
        }

        $this->actionParams = $actionParams;

        return $args;
    }

    /**
     * {@inheritdoc}
     */
    public function beforeAction($action)
    {
        if (parent::beforeAction($action)) {
            if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
                throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
            }

            return true;
        }

        return false;
    }

    /**
     * Redirects the browser to the specified URL.
     * This method is a shortcut to [[Response::redirect()]].
     *
     * You can use it in an action by returning the [[Response]] directly:
     *
     * ```php
     * // stop executing this action and redirect to login page
     * return $this->redirect(['login']);
     * ```
     *
     * @param string|array $url the URL to be redirected to. This can be in one of the following formats:
     *
     * - a string representing a URL (e.g. "http://example.com")
     * - a string representing a URL alias (e.g. "@example.com")
     * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`)
     *   [[Url::to()]] will be used to convert the array into a URL.
     *
     * Any relative URL that starts with a single forward slash "/" will be converted
     * into an absolute one by prepending it with the host info of the current request.
     *
     * @param int $statusCode the HTTP status code. Defaults to 302.
     * See <https://tools.ietf.org/html/rfc2616#section-10>
     * for details about HTTP status code
     * @return Response the current response object
     */
    public function redirect($url, $statusCode = 302)
    {
        // calling Url::to() here because Response::redirect() modifies route before calling Url::to()
        return Yii::$app->getResponse()->redirect(Url::to($url), $statusCode);
    }

    /**
     * Redirects the browser to the home page.
     *
     * You can use this method in an action by returning the [[Response]] directly:
     *
     * ```php
     * // stop executing this action and redirect to home page
     * return $this->goHome();
     * ```
     *
     * @return Response the current response object
     */
    public function goHome()
    {
        return Yii::$app->getResponse()->redirect(Yii::$app->getHomeUrl());
    }

    /**
     * Redirects the browser to the last visited page.
     *
     * You can use this method in an action by returning the [[Response]] directly:
     *
     * ```php
     * // stop executing this action and redirect to last visited page
     * return $this->goBack();
     * ```
     *
     * For this function to work you have to [[User::setReturnUrl()|set the return URL]] in appropriate places before.
     *
     * @param string|array $defaultUrl the default return URL in case it was not set previously.
     * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
     * Please refer to [[User::setReturnUrl()]] on accepted format of the URL.
     * @return Response the current response object
     * @see User::getReturnUrl()
     */
    public function goBack($defaultUrl = null)
    {
        return Yii::$app->getResponse()->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl));
    }

    /**
     * Refreshes the current page.
     * This method is a shortcut to [[Response::refresh()]].
     *
     * You can use it in an action by returning the [[Response]] directly:
     *
     * ```php
     * // stop executing this action and refresh the current page
     * return $this->refresh();
     * ```
     *
     * @param string $anchor the anchor that should be appended to the redirection URL.
     * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
     * @return Response the response object itself
     */
    public function refresh($anchor = '')
    {
        return Yii::$app->getResponse()->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\base;

/**
 * Behavior is the base class for all behavior classes.
 *
 * A behavior can be used to enhance the functionality of an existing component without modifying its code.
 * In particular, it can "inject" its own methods and properties into the component
 * and make them directly accessible via the component. It can also respond to the events triggered in the component
 * and thus intercept the normal code execution.
 *
 * For more details and usage information on Behavior, see the [guide article on behaviors](guide:concept-behaviors).
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class Behavior extends BaseObject
{
    /**
     * @var Component|null the owner of this behavior
     */
    public $owner;

    /**
     * Declares event handlers for the [[owner]]'s events.
     *
     * Child classes may override this method to declare what PHP callbacks should
     * be attached to the events of the [[owner]] component.
     *
     * The callbacks will be attached to the [[owner]]'s events when the behavior is
     * attached to the owner; and they will be detached from the events when
     * the behavior is detached from the component.
     *
     * The callbacks can be any of the following:
     *
     * - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']`
     * - object method: `[$object, 'handleClick']`
     * - static method: `['Page', 'handleClick']`
     * - anonymous function: `function ($event) { ... }`
     *
     * The following is an example:
     *
     * ```php
     * [
     *     Model::EVENT_BEFORE_VALIDATE => 'myBeforeValidate',
     *     Model::EVENT_AFTER_VALIDATE => 'myAfterValidate',
     * ]
     * ```
     *
     * @return array events (array keys) and the corresponding event handler methods (array values).
     */
    public function events()
    {
        return [];
    }

    /**
     * Attaches the behavior object to the component.
     * The default implementation will set the [[owner]] property
     * and attach event handlers as declared in [[events]].
     * Make sure you call the parent implementation if you override this method.
     * @param Component $owner the component that this behavior is to be attached to.
     */
    public function attach($owner)
    {
        $this->owner = $owner;
        foreach ($this->events() as $event => $handler) {
            $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
        }
    }

    /**
     * Detaches the behavior object from the component.
     * The default implementation will unset the [[owner]] property
     * and detach event handlers declared in [[events]].
     * Make sure you call the parent implementation if you override this method.
     */
    public function detach()
    {
        if ($this->owner) {
            foreach ($this->events() as $event => $handler) {
                $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
            }
            $this->owner = null;
        }
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\web;

use Yii;
use yii\base\BaseObject;
use yii\base\InvalidConfigException;

/**
 * UrlRule represents a rule used by [[UrlManager]] for parsing and generating URLs.
 *
 * To define your own URL parsing and creation logic you can extend from this class
 * and add it to [[UrlManager::rules]] like this:
 *
 * ```php
 * 'rules' => [
 *     ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...],
 *     // ...
 * ]
 * ```
 *
 * @property null|int $createUrlStatus Status of the URL creation after the last [[createUrl()]] call. `null`
 * if rule does not provide info about create status. This property is read-only.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class UrlRule extends BaseObject implements UrlRuleInterface
{
    /**
     * Set [[mode]] with this value to mark that this rule is for URL parsing only.
     */
    const PARSING_ONLY = 1;
    /**
     * Set [[mode]] with this value to mark that this rule is for URL creation only.
     */
    const CREATION_ONLY = 2;
    /**
     * Represents the successful URL generation by last [[createUrl()]] call.
     * @see $createStatus
     * @since 2.0.12
     */
    const CREATE_STATUS_SUCCESS = 0;
    /**
     * Represents the unsuccessful URL generation by last [[createUrl()]] call, because rule does not support
     * creating URLs.
     * @see $createStatus
     * @since 2.0.12
     */
    const CREATE_STATUS_PARSING_ONLY = 1;
    /**
     * Represents the unsuccessful URL generation by last [[createUrl()]] call, because of mismatched route.
     * @see $createStatus
     * @since 2.0.12
     */
    const CREATE_STATUS_ROUTE_MISMATCH = 2;
    /**
     * Represents the unsuccessful URL generation by last [[createUrl()]] call, because of mismatched
     * or missing parameters.
     * @see $createStatus
     * @since 2.0.12
     */
    const CREATE_STATUS_PARAMS_MISMATCH = 4;

    /**
     * @var string the name of this rule. If not set, it will use [[pattern]] as the name.
     */
    public $name;
    /**
     * On the rule initialization, the [[pattern]] matching parameters names will be replaced with [[placeholders]].
     * @var string the pattern used to parse and create the path info part of a URL.
     * @see host
     * @see placeholders
     */
    public $pattern;
    /**
     * @var string the pattern used to parse and create the host info part of a URL (e.g. `http://example.com`).
     * @see pattern
     */
    public $host;
    /**
     * @var string the route to the controller action
     */
    public $route;
    /**
     * @var array the default GET parameters (name => value) that this rule provides.
     * When this rule is used to parse the incoming request, the values declared in this property
     * will be injected into $_GET.
     */
    public $defaults = [];
    /**
     * @var string the URL suffix used for this rule.
     * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
     * If not set, the value of [[UrlManager::suffix]] will be used.
     */
    public $suffix;
    /**
     * @var string|array the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
     * Use array to represent multiple verbs that this rule may match.
     * If this property is not set, the rule can match any verb.
     * Note that this property is only used when parsing a request. It is ignored for URL creation.
     */
    public $verb;
    /**
     * @var int a value indicating if this rule should be used for both request parsing and URL creation,
     * parsing only, or creation only.
     * If not set or 0, it means the rule is both request parsing and URL creation.
     * If it is [[PARSING_ONLY]], the rule is for request parsing only.
     * If it is [[CREATION_ONLY]], the rule is for URL creation only.
     */
    public $mode;
    /**
     * @var bool a value indicating if parameters should be url encoded.
     */
    public $encodeParams = true;
    /**
     * @var UrlNormalizer|array|false|null the configuration for [[UrlNormalizer]] used by this rule.
     * If `null`, [[UrlManager::normalizer]] will be used, if `false`, normalization will be skipped
     * for this rule.
     * @since 2.0.10
     */
    public $normalizer;

    /**
     * @var int|null status of the URL creation after the last [[createUrl()]] call.
     * @since 2.0.12
     */
    protected $createStatus;
    /**
     * @var array list of placeholders for matching parameters names. Used in [[parseRequest()]], [[createUrl()]].
     * On the rule initialization, the [[pattern]] parameters names will be replaced with placeholders.
     * This array contains relations between the original parameters names and their placeholders.
     * The array keys are the placeholders and the values are the original names.
     *
     * @see parseRequest()
     * @see createUrl()
     * @since 2.0.7
     */
    protected $placeholders = [];

    /**
     * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
     */
    private $_template;
    /**
     * @var string the regex for matching the route part. This is used in generating URL.
     */
    private $_routeRule;
    /**
     * @var array list of regex for matching parameters. This is used in generating URL.
     */
    private $_paramRules = [];
    /**
     * @var array list of parameters used in the route.
     */
    private $_routeParams = [];

    /**
     * @return string
     * @since 2.0.11
     */
    public function __toString()
    {
        $str = '';
        if ($this->verb !== null) {
            $str .= implode(',', $this->verb) . ' ';
        }
        if ($this->host !== null && strrpos($this->name, $this->host) === false) {
            $str .= $this->host . '/';
        }
        $str .= $this->name;

        if ($str === '') {
            return '/';
        }

        return $str;
    }

    /**
     * Initializes this rule.
     */
    public function init()
    {
        if ($this->pattern === null) {
            throw new InvalidConfigException('UrlRule::pattern must be set.');
        }
        if ($this->route === null) {
            throw new InvalidConfigException('UrlRule::route must be set.');
        }
        if (is_array($this->normalizer)) {
            $normalizerConfig = array_merge(['class' => UrlNormalizer::className()], $this->normalizer);
            $this->normalizer = Yii::createObject($normalizerConfig);
        }
        if ($this->normalizer !== null && $this->normalizer !== false && !$this->normalizer instanceof UrlNormalizer) {
            throw new InvalidConfigException('Invalid config for UrlRule::normalizer.');
        }
        if ($this->verb !== null) {
            if (is_array($this->verb)) {
                foreach ($this->verb as $i => $verb) {
                    $this->verb[$i] = strtoupper($verb);
                }
            } else {
                $this->verb = [strtoupper($this->verb)];
            }
        }
        if ($this->name === null) {
            $this->name = $this->pattern;
        }

        $this->preparePattern();
    }

    /**
     * Process [[$pattern]] on rule initialization.
     */
    private function preparePattern()
    {
        $this->pattern = $this->trimSlashes($this->pattern);
        $this->route = trim($this->route, '/');

        if ($this->host !== null) {
            $this->host = rtrim($this->host, '/');
            $this->pattern = rtrim($this->host . '/' . $this->pattern, '/');
        } elseif ($this->pattern === '') {
            $this->_template = '';
            $this->pattern = '#^$#u';

            return;
        } elseif (($pos = strpos($this->pattern, '://')) !== false) {
            if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) {
                $this->host = substr($this->pattern, 0, $pos2);
            } else {
                $this->host = $this->pattern;
            }
        } elseif (strncmp($this->pattern, '//', 2) === 0) {
            if (($pos2 = strpos($this->pattern, '/', 2)) !== false) {
                $this->host = substr($this->pattern, 0, $pos2);
            } else {
                $this->host = $this->pattern;
            }
        } else {
            $this->pattern = '/' . $this->pattern . '/';
        }

        if (strpos($this->route, '<') !== false && preg_match_all('/<([\w._-]+)>/', $this->route, $matches)) {
            foreach ($matches[1] as $name) {
                $this->_routeParams[$name] = "<$name>";
            }
        }

        $this->translatePattern(true);
    }

    /**
     * Prepares [[$pattern]] on rule initialization - replace parameter names by placeholders.
     *
     * @param bool $allowAppendSlash Defines position of slash in the param pattern in [[$pattern]].
     * If `false` slash will be placed at the beginning of param pattern. If `true` slash position will be detected
     * depending on non-optional pattern part.
     */
    private function translatePattern($allowAppendSlash)
    {
        $tr = [
            '.' => '\\.',
            '*' => '\\*',
            '$' => '\\$',
            '[' => '\\[',
            ']' => '\\]',
            '(' => '\\(',
            ')' => '\\)',
        ];

        $tr2 = [];
        $requiredPatternPart = $this->pattern;
        $oldOffset = 0;
        if (preg_match_all('/<([\w._-]+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
            $appendSlash = false;
            foreach ($matches as $match) {
                $name = $match[1][0];
                $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
                $placeholder = 'a' . hash('crc32b', $name); // placeholder must begin with a letter
                $this->placeholders[$placeholder] = $name;
                if (array_key_exists($name, $this->defaults)) {
                    $length = strlen($match[0][0]);
                    $offset = $match[0][1];
                    $requiredPatternPart = str_replace("/{$match[0][0]}/", '//', $requiredPatternPart);
                    if (
                        $allowAppendSlash
                        && ($appendSlash || $offset === 1)
                        && (($offset - $oldOffset) === 1)
                        && isset($this->pattern[$offset + $length])
                        && $this->pattern[$offset + $length] === '/'
                        && isset($this->pattern[$offset + $length + 1])
                    ) {
                        // if pattern starts from optional params, put slash at the end of param pattern
                        // @see https://github.com/yiisoft/yii2/issues/13086
                        $appendSlash = true;
                        $tr["<$name>/"] = "((?P<$placeholder>$pattern)/)?";
                    } elseif (
                        $offset > 1
                        && $this->pattern[$offset - 1] === '/'
                        && (!isset($this->pattern[$offset + $length]) || $this->pattern[$offset + $length] === '/')
                    ) {
                        $appendSlash = false;
                        $tr["/<$name>"] = "(/(?P<$placeholder>$pattern))?";
                    }
                    $tr["<$name>"] = "(?P<$placeholder>$pattern)?";
                    $oldOffset = $offset + $length;
                } else {
                    $appendSlash = false;
                    $tr["<$name>"] = "(?P<$placeholder>$pattern)";
                }

                if (isset($this->_routeParams[$name])) {
                    $tr2["<$name>"] = "(?P<$placeholder>$pattern)";
                } else {
                    $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#u";
                }
            }
        }

        // we have only optional params in route - ensure slash position on param patterns
        if ($allowAppendSlash && trim($requiredPatternPart, '/') === '') {
            $this->translatePattern(false);
            return;
        }

        $this->_template = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $this->pattern);
        $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';

        // if host starts with relative scheme, then insert pattern to match any
        if (strncmp($this->host, '//', 2) === 0) {
            $this->pattern = substr_replace($this->pattern, '[\w]+://', 2, 0);
        }

        if (!empty($this->_routeParams)) {
            $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
        }
    }

    /**
     * @param UrlManager $manager the URL manager
     * @return UrlNormalizer|null
     * @since 2.0.10
     */
    protected function getNormalizer($manager)
    {
        if ($this->normalizer === null) {
            return $manager->normalizer;
        }

        return $this->normalizer;
    }

    /**
     * @param UrlManager $manager the URL manager
     * @return bool
     * @since 2.0.10
     */
    protected function hasNormalizer($manager)
    {
        return $this->getNormalizer($manager) instanceof UrlNormalizer;
    }

    /**
     * Parses the given request and returns the corresponding route and parameters.
     * @param UrlManager $manager the URL manager
     * @param Request $request the request component
     * @return array|bool the parsing result. The route and the parameters are returned as an array.
     * If `false`, it means this rule cannot be used to parse this path info.
     */
    public function parseRequest($manager, $request)
    {
        if ($this->mode === self::CREATION_ONLY) {
            return false;
        }

        if (!empty($this->verb) && !in_array($request->getMethod(), $this->verb, true)) {
            return false;
        }

        $suffix = (string) ($this->suffix === null ? $manager->suffix : $this->suffix);
        $pathInfo = $request->getPathInfo();
        $normalized = false;
        if ($this->hasNormalizer($manager)) {
            $pathInfo = $this->getNormalizer($manager)->normalizePathInfo($pathInfo, $suffix, $normalized);
        }
        if ($suffix !== '' && $pathInfo !== '') {
            $n = strlen($suffix);
            if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) {
                $pathInfo = substr($pathInfo, 0, -$n);
                if ($pathInfo === '') {
                    // suffix alone is not allowed
                    return false;
                }
            } else {
                return false;
            }
        }

        if ($this->host !== null) {
            $pathInfo = strtolower($request->getHostInfo()) . ($pathInfo === '' ? '' : '/' . $pathInfo);
        }

        if (!preg_match($this->pattern, $pathInfo, $matches)) {
            return false;
        }
        $matches = $this->substitutePlaceholderNames($matches);

        foreach ($this->defaults as $name => $value) {
            if (!isset($matches[$name]) || $matches[$name] === '') {
                $matches[$name] = $value;
            }
        }
        $params = $this->defaults;
        $tr = [];
        foreach ($matches as $name => $value) {
            if (isset($this->_routeParams[$name])) {
                $tr[$this->_routeParams[$name]] = $value;
                unset($params[$name]);
            } elseif (isset($this->_paramRules[$name])) {
                $params[$name] = $value;
            }
        }
        if ($this->_routeRule !== null) {
            $route = strtr($this->route, $tr);
        } else {
            $route = $this->route;
        }

        Yii::debug("Request parsed with URL rule: {$this->name}", __METHOD__);

        if ($normalized) {
            // pathInfo was changed by normalizer - we need also normalize route
            return $this->getNormalizer($manager)->normalizeRoute([$route, $params]);
        }

        return [$route, $params];
    }

    /**
     * Creates a URL according to the given route and parameters.
     * @param UrlManager $manager the URL manager
     * @param string $route the route. It should not have slashes at the beginning or the end.
     * @param array $params the parameters
     * @return string|bool the created URL, or `false` if this rule cannot be used for creating this URL.
     */
    public function createUrl($manager, $route, $params)
    {
        if ($this->mode === self::PARSING_ONLY) {
            $this->createStatus = self::CREATE_STATUS_PARSING_ONLY;
            return false;
        }

        $tr = [];

        // match the route part first
        if ($route !== $this->route) {
            if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
                $matches = $this->substitutePlaceholderNames($matches);
                foreach ($this->_routeParams as $name => $token) {
                    if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
                        $tr[$token] = '';
                    } else {
                        $tr[$token] = $matches[$name];
                    }
                }
            } else {
                $this->createStatus = self::CREATE_STATUS_ROUTE_MISMATCH;
                return false;
            }
        }

        // match default params
        // if a default param is not in the route pattern, its value must also be matched
        foreach ($this->defaults as $name => $value) {
            if (isset($this->_routeParams[$name])) {
                continue;
            }
            if (!isset($params[$name])) {
                // allow omit empty optional params
                // @see https://github.com/yiisoft/yii2/issues/10970
                if (in_array($name, $this->placeholders) && strcmp($value, '') === 0) {
                    $params[$name] = '';
                } else {
                    $this->createStatus = self::CREATE_STATUS_PARAMS_MISMATCH;
                    return false;
                }
            }
            if (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
                unset($params[$name]);
                if (isset($this->_paramRules[$name])) {
                    $tr["<$name>"] = '';
                }
            } elseif (!isset($this->_paramRules[$name])) {
                $this->createStatus = self::CREATE_STATUS_PARAMS_MISMATCH;
                return false;
            }
        }

        // match params in the pattern
        foreach ($this->_paramRules as $name => $rule) {
            if (isset($params[$name]) && !is_array($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
                $tr["<$name>"] = $this->encodeParams ? urlencode($params[$name]) : $params[$name];
                unset($params[$name]);
            } elseif (!isset($this->defaults[$name]) || isset($params[$name])) {
                $this->createStatus = self::CREATE_STATUS_PARAMS_MISMATCH;
                return false;
            }
        }

        $url = $this->trimSlashes(strtr($this->_template, $tr));
        if ($this->host !== null) {
            $pos = strpos($url, '/', 8);
            if ($pos !== false) {
                $url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos));
            }
        } elseif (strpos($url, '//') !== false) {
            $url = preg_replace('#/+#', '/', trim($url, '/'));
        }

        if ($url !== '') {
            $url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
        }

        if (!empty($params) && ($query = http_build_query($params)) !== '') {
            $url .= '?' . $query;
        }

        $this->createStatus = self::CREATE_STATUS_SUCCESS;
        return $url;
    }

    /**
     * Returns status of the URL creation after the last [[createUrl()]] call.
     *
     * @return null|int Status of the URL creation after the last [[createUrl()]] call. `null` if rule does not provide
     * info about create status.
     * @see $createStatus
     * @since 2.0.12
     */
    public function getCreateUrlStatus()
    {
        return $this->createStatus;
    }

    /**
     * Returns list of regex for matching parameter.
     * @return array parameter keys and regexp rules.
     *
     * @since 2.0.6
     */
    protected function getParamRules()
    {
        return $this->_paramRules;
    }

    /**
     * Iterates over [[placeholders]] and checks whether each placeholder exists as a key in $matches array.
     * When found - replaces this placeholder key with a appropriate name of matching parameter.
     * Used in [[parseRequest()]], [[createUrl()]].
     *
     * @param array $matches result of `preg_match()` call
     * @return array input array with replaced placeholder keys
     * @see placeholders
     * @since 2.0.7
     */
    protected function substitutePlaceholderNames(array $matches)
    {
        foreach ($this->placeholders as $placeholder => $name) {
            if (isset($matches[$placeholder])) {
                $matches[$name] = $matches[$placeholder];
                unset($matches[$placeholder]);
            }
        }

        return $matches;
    }

    /**
     * Trim slashes in passed string. If string begins with '//', two slashes are left as is
     * in the beginning of a string.
     *
     * @param string $string
     * @return string
     */
    private function trimSlashes($string)
    {
        if (strncmp($string, '//', 2) === 0) {
            return '//' . trim($string, '/');
        }

        return trim($string, '/');
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\grid;

use Closure;
use yii\base\BaseObject;
use yii\helpers\Html;

/**
 * Column is the base class of all [[GridView]] column classes.
 *
 * For more details and usage information on Column, see the [guide article on data widgets](guide:output-data-widgets).
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class Column extends BaseObject
{
    /**
     * @var GridView the grid view object that owns this column.
     */
    public $grid;
    /**
     * @var string the header cell content. Note that it will not be HTML-encoded.
     */
    public $header;
    /**
     * @var string the footer cell content. Note that it will not be HTML-encoded.
     */
    public $footer;
    /**
     * @var callable This is a callable that will be used to generate the content of each cell.
     * The signature of the function should be the following: `function ($model, $key, $index, $column)`.
     * Where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered
     * and `$column` is a reference to the [[Column]] object.
     */
    public $content;
    /**
     * @var bool whether this column is visible. Defaults to true.
     */
    public $visible = true;
    /**
     * @var array the HTML attributes for the column group tag.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $options = [];
    /**
     * @var array the HTML attributes for the header cell tag.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $headerOptions = [];
    /**
     * @var array|\Closure the HTML attributes for the data cell tag. This can either be an array of
     * attributes or an anonymous function ([[Closure]]) that returns such an array.
     * The signature of the function should be the following: `function ($model, $key, $index, $column)`.
     * Where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered
     * and `$column` is a reference to the [[Column]] object.
     * A function may be used to assign different attributes to different rows based on the data in that row.
     *
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $contentOptions = [];
    /**
     * @var array the HTML attributes for the footer cell tag.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $footerOptions = [];
    /**
     * @var array the HTML attributes for the filter cell tag.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $filterOptions = [];

    /**
     * Renders the header cell.
     */
    public function renderHeaderCell()
    {
        return Html::tag('th', $this->renderHeaderCellContent(), $this->headerOptions);
    }

    /**
     * Renders the footer cell.
     */
    public function renderFooterCell()
    {
        return Html::tag('td', $this->renderFooterCellContent(), $this->footerOptions);
    }

    /**
     * Renders a data cell.
     * @param mixed $model the data model being rendered
     * @param mixed $key the key associated with the data model
     * @param int $index the zero-based index of the data item among the item array returned by [[GridView::dataProvider]].
     * @return string the rendering result
     */
    public function renderDataCell($model, $key, $index)
    {
        if ($this->contentOptions instanceof Closure) {
            $options = call_user_func($this->contentOptions, $model, $key, $index, $this);
        } else {
            $options = $this->contentOptions;
        }

        return Html::tag('td', $this->renderDataCellContent($model, $key, $index), $options);
    }

    /**
     * Renders the filter cell.
     */
    public function renderFilterCell()
    {
        return Html::tag('td', $this->renderFilterCellContent(), $this->filterOptions);
    }

    /**
     * Renders the header cell content.
     * The default implementation simply renders [[header]].
     * This method may be overridden to customize the rendering of the header cell.
     * @return string the rendering result
     */
    protected function renderHeaderCellContent()
    {
        return trim($this->header) !== '' ? $this->header : $this->getHeaderCellLabel();
    }

    /**
     * Returns header cell label.
     * This method may be overridden to customize the label of the header cell.
     * @return string label
     * @since 2.0.8
     */
    protected function getHeaderCellLabel()
    {
        return $this->grid->emptyCell;
    }

    /**
     * Renders the footer cell content.
     * The default implementation simply renders [[footer]].
     * This method may be overridden to customize the rendering of the footer cell.
     * @return string the rendering result
     */
    protected function renderFooterCellContent()
    {
        return trim($this->footer) !== '' ? $this->footer : $this->grid->emptyCell;
    }

    /**
     * Renders the data cell content.
     * @param mixed $model the data model
     * @param mixed $key the key associated with the data model
     * @param int $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]].
     * @return string the rendering result
     */
    protected function renderDataCellContent($model, $key, $index)
    {
        if ($this->content !== null) {
            return call_user_func($this->content, $model, $key, $index, $this);
        }

        return $this->grid->emptyCell;
    }

    /**
     * Renders the filter cell content.
     * The default implementation simply renders a space.
     * This method may be overridden to customize the rendering of the filter cell (if any).
     * @return string the rendering result
     */
    protected function renderFilterCellContent()
    {
        return $this->grid->emptyCell;
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\console\controllers;

use Yii;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\console\Controller;
use yii\console\Exception;
use yii\console\ExitCode;
use yii\helpers\Console;
use yii\helpers\FileHelper;
use yii\test\FixtureTrait;

/**
 * Manages fixture data loading and unloading.
 *
 * ```
 * #load fixtures from UsersFixture class with default namespace "tests\unit\fixtures"
 * yii fixture/load User
 *
 * #also a short version of this command (generate action is default)
 * yii fixture User
 *
 * #load all fixtures
 * yii fixture "*"
 *
 * #load all fixtures except User
 * yii fixture "*, -User"
 *
 * #load fixtures with different namespace.
 * yii fixture/load User --namespace=alias\my\custom\namespace\goes\here
 * ```
 *
 * The `unload` sub-command can be used similarly to unload fixtures.
 *
 * @author Mark Jebri <mark.github@yandex.ru>
 * @since 2.0
 */
class FixtureController extends Controller
{
    use FixtureTrait;

    /**
     * @var string controller default action ID.
     */
    public $defaultAction = 'load';
    /**
     * @var string default namespace to search fixtures in
     */
    public $namespace = 'tests\unit\fixtures';
    /**
     * @var array global fixtures that should be applied when loading and unloading. By default it is set to `InitDbFixture`
     * that disables and enables integrity check, so your data can be safely loaded.
     */
    public $globalFixtures = [
        'yii\test\InitDbFixture',
    ];

    /**
     * {@inheritdoc}
     */
    public function options($actionID)
    {
        return array_merge(parent::options($actionID), [
            'namespace', 'globalFixtures',
        ]);
    }

    /**
     * {@inheritdoc}
     * @since 2.0.8
     */
    public function optionAliases()
    {
        return array_merge(parent::optionAliases(), [
            'g' => 'globalFixtures',
            'n' => 'namespace',
        ]);
    }

    /**
     * Loads the specified fixture data.
     *
     * For example,
     *
     * ```
     * # load the fixture data specified by User and UserProfile.
     * # any existing fixture data will be removed first
     * yii fixture/load "User, UserProfile"
     *
     * # load all available fixtures found under 'tests\unit\fixtures'
     * yii fixture/load "*"
     *
     * # load all fixtures except User and UserProfile
     * yii fixture/load "*, -User, -UserProfile"
     * ```
     *
     * @param array $fixturesInput
     * @return int return code
     * @throws Exception if the specified fixture does not exist.
     */
    public function actionLoad(array $fixturesInput = [])
    {
        if ($fixturesInput === []) {
            $this->printHelpMessage();
            return ExitCode::OK;
        }

        $filtered = $this->filterFixtures($fixturesInput);
        $except = $filtered['except'];

        if (!$this->needToApplyAll($fixturesInput[0])) {
            $fixtures = $filtered['apply'];

            $foundFixtures = $this->findFixtures($fixtures);
            $notFoundFixtures = array_diff($fixtures, $foundFixtures);

            if ($notFoundFixtures) {
                $this->notifyNotFound($notFoundFixtures);
            }
        } else {
            $foundFixtures = $this->findFixtures();
        }

        $fixturesToLoad = array_diff($foundFixtures, $except);

        if (!$foundFixtures) {
            throw new Exception(
                'No files were found for: "' . implode(', ', $fixturesInput) . "\".\n" .
                "Check that files exist under fixtures path: \n\"" . $this->getFixturePath() . '".'
            );
        }

        if (!$fixturesToLoad) {
            $this->notifyNothingToLoad($foundFixtures, $except);
            return ExitCode::OK;
        }

        if (!$this->confirmLoad($fixturesToLoad, $except)) {
            return ExitCode::OK;
        }

        $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $fixturesToLoad));

        if (!$fixtures) {
            throw new Exception('No fixtures were found in namespace: "' . $this->namespace . '"' . '');
        }

        $fixturesObjects = $this->createFixtures($fixtures);

        $this->unloadFixtures($fixturesObjects);
        $this->loadFixtures($fixturesObjects);
        $this->notifyLoaded($fixtures);

        return ExitCode::OK;
    }

    /**
     * Unloads the specified fixtures.
     *
     * For example,
     *
     * ```
     * # unload the fixture data specified by User and UserProfile.
     * yii fixture/unload "User, UserProfile"
     *
     * # unload all fixtures found under 'tests\unit\fixtures'
     * yii fixture/unload "*"
     *
     * # unload all fixtures except User and UserProfile
     * yii fixture/unload "*, -User, -UserProfile"
     * ```
     *
     * @param array $fixturesInput
     * @return int return code
     * @throws Exception if the specified fixture does not exist.
     */
    public function actionUnload(array $fixturesInput = [])
    {
        if ($fixturesInput === []) {
            $this->printHelpMessage();
            return ExitCode::OK;
        }

        $filtered = $this->filterFixtures($fixturesInput);
        $except = $filtered['except'];

        if (!$this->needToApplyAll($fixturesInput[0])) {
            $fixtures = $filtered['apply'];

            $foundFixtures = $this->findFixtures($fixtures);
            $notFoundFixtures = array_diff($fixtures, $foundFixtures);

            if ($notFoundFixtures) {
                $this->notifyNotFound($notFoundFixtures);
            }
        } else {
            $foundFixtures = $this->findFixtures();
        }

        $fixturesToUnload = array_diff($foundFixtures, $except);

        if (!$foundFixtures) {
            throw new Exception(
                'No files were found for: "' . implode(', ', $fixturesInput) . "\".\n" .
                "Check that files exist under fixtures path: \n\"" . $this->getFixturePath() . '".'
            );
        }

        if (!$fixturesToUnload) {
            $this->notifyNothingToUnload($foundFixtures, $except);
            return ExitCode::OK;
        }

        if (!$this->confirmUnload($fixturesToUnload, $except)) {
            return ExitCode::OK;
        }

        $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $fixturesToUnload));

        if (!$fixtures) {
            throw new Exception('No fixtures were found in namespace: ' . $this->namespace . '".');
        }

        $this->unloadFixtures($this->createFixtures($fixtures));
        $this->notifyUnloaded($fixtures);
    }

    /**
     * Show help message.
     * @param array $fixturesInput
     */
    private function printHelpMessage()
    {
        $this->stdout($this->getHelpSummary() . "\n");

        $helpCommand = Console::ansiFormat('yii help fixture', [Console::FG_CYAN]);
        $this->stdout("Use $helpCommand to get usage info.\n");
    }

    /**
     * Notifies user that fixtures were successfully loaded.
     * @param array $fixtures
     */
    private function notifyLoaded($fixtures)
    {
        $this->stdout("Fixtures were successfully loaded from namespace:\n", Console::FG_YELLOW);
        $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
        $this->outputList($fixtures);
    }

    /**
     * Notifies user that there are no fixtures to load according input conditions.
     * @param array $foundFixtures array of found fixtures
     * @param array $except array of names of fixtures that should not be loaded
     */
    public function notifyNothingToLoad($foundFixtures, $except)
    {
        $this->stdout("Fixtures to load could not be found according given conditions:\n\n", Console::FG_RED);
        $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
        $this->stdout("\t" . $this->namespace . "\n", Console::FG_GREEN);

        if (count($foundFixtures)) {
            $this->stdout("\nFixtures founded under the namespace:\n\n", Console::FG_YELLOW);
            $this->outputList($foundFixtures);
        }

        if (count($except)) {
            $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
            $this->outputList($except);
        }
    }

    /**
     * Notifies user that there are no fixtures to unload according input conditions.
     * @param array $foundFixtures array of found fixtures
     * @param array $except array of names of fixtures that should not be loaded
     */
    public function notifyNothingToUnload($foundFixtures, $except)
    {
        $this->stdout("Fixtures to unload could not be found according to given conditions:\n\n", Console::FG_RED);
        $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
        $this->stdout("\t" . $this->namespace . "\n", Console::FG_GREEN);

        if (count($foundFixtures)) {
            $this->stdout("\nFixtures found under the namespace:\n\n", Console::FG_YELLOW);
            $this->outputList($foundFixtures);
        }

        if (count($except)) {
            $this->stdout("\nFixtures that will NOT be unloaded: \n\n", Console::FG_YELLOW);
            $this->outputList($except);
        }
    }

    /**
     * Notifies user that fixtures were successfully unloaded.
     * @param array $fixtures
     */
    private function notifyUnloaded($fixtures)
    {
        $this->stdout("\nFixtures were successfully unloaded from namespace: ", Console::FG_YELLOW);
        $this->stdout(Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
        $this->outputList($fixtures);
    }

    /**
     * Notifies user that fixtures were not found under fixtures path.
     * @param array $fixtures
     */
    private function notifyNotFound($fixtures)
    {
        $this->stdout("Some fixtures were not found under path:\n", Console::BG_RED);
        $this->stdout("\t" . $this->getFixturePath() . "\n\n", Console::FG_GREEN);
        $this->stdout("Check that they have correct namespace \"{$this->namespace}\" \n", Console::BG_RED);
        $this->outputList($fixtures);
        $this->stdout("\n");
    }

    /**
     * Prompts user with confirmation if fixtures should be loaded.
     * @param array $fixtures
     * @param array $except
     * @return bool
     */
    private function confirmLoad($fixtures, $except)
    {
        $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
        $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);

        if (count($this->globalFixtures)) {
            $this->stdout("Global fixtures will be used:\n\n", Console::FG_YELLOW);
            $this->outputList($this->globalFixtures);
        }

        if (count($fixtures)) {
            $this->stdout("\nFixtures below will be loaded:\n\n", Console::FG_YELLOW);
            $this->outputList($fixtures);
        }

        if (count($except)) {
            $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
            $this->outputList($except);
        }

        $this->stdout("\nBe aware that:\n", Console::BOLD);
        $this->stdout("Applying leads to purging of certain data in the database!\n", Console::FG_RED);

        return $this->confirm("\nLoad above fixtures?");
    }

    /**
     * Prompts user with confirmation for fixtures that should be unloaded.
     * @param array $fixtures
     * @param array $except
     * @return bool
     */
    private function confirmUnload($fixtures, $except)
    {
        $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
        $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);

        if (count($this->globalFixtures)) {
            $this->stdout("Global fixtures will be used:\n\n", Console::FG_YELLOW);
            $this->outputList($this->globalFixtures);
        }

        if (count($fixtures)) {
            $this->stdout("\nFixtures below will be unloaded:\n\n", Console::FG_YELLOW);
            $this->outputList($fixtures);
        }

        if (count($except)) {
            $this->stdout("\nFixtures that will NOT be unloaded:\n\n", Console::FG_YELLOW);
            $this->outputList($except);
        }

        return $this->confirm("\nUnload fixtures?");
    }

    /**
     * Outputs data to the console as a list.
     * @param array $data
     */
    private function outputList($data)
    {
        foreach ($data as $index => $item) {
            $this->stdout("\t" . ($index + 1) . ". {$item}\n", Console::FG_GREEN);
        }
    }

    /**
     * Checks if needed to apply all fixtures.
     * @param string $fixture
     * @return bool
     */
    public function needToApplyAll($fixture)
    {
        return $fixture === '*';
    }

    /**
     * Finds fixtures to be loaded, for example "User", if no fixtures were specified then all of them
     * will be searching by suffix "Fixture.php".
     * @param array $fixtures fixtures to be loaded
     * @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists.
     */
    private function findFixtures(array $fixtures = [])
    {
        $fixturesPath = $this->getFixturePath();

        $filesToSearch = ['*Fixture.php'];
        $findAll = ($fixtures === []);

        if (!$findAll) {
            $filesToSearch = [];

            foreach ($fixtures as $fileName) {
                $filesToSearch[] = $fileName . 'Fixture.php';
            }
        }

        $files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]);
        $foundFixtures = [];

        foreach ($files as $fixture) {
            $foundFixtures[] = $this->getFixtureRelativeName($fixture);
        }

        return $foundFixtures;
    }

    /**
     * Calculates fixture's name
     * Basically, strips [[getFixturePath()]] and `Fixture.php' suffix from fixture's full path.
     * @see getFixturePath()
     * @param string $fullFixturePath Full fixture path
     * @return string Relative fixture name
     */
    private function getFixtureRelativeName($fullFixturePath)
    {
        $fixturesPath = FileHelper::normalizePath($this->getFixturePath());
        $fullFixturePath = FileHelper::normalizePath($fullFixturePath);

        $relativeName = substr($fullFixturePath, strlen($fixturesPath) + 1);
        $relativeDir = dirname($relativeName) === '.' ? '' : dirname($relativeName) . DIRECTORY_SEPARATOR;

        return $relativeDir . basename($fullFixturePath, 'Fixture.php');
    }

    /**
     * Returns valid fixtures config that can be used to load them.
     * @param array $fixtures fixtures to configure
     * @return array
     */
    private function getFixturesConfig($fixtures)
    {
        $config = [];

        foreach ($fixtures as $fixture) {
            $isNamespaced = (strpos($fixture, '\\') !== false);
            // replace linux' path slashes to namespace backslashes, in case if $fixture is non-namespaced relative path
            $fixture = str_replace('/', '\\', $fixture);
            $fullClassName = $isNamespaced ? $fixture : $this->namespace . '\\' . $fixture;

            if (class_exists($fullClassName)) {
                $config[] = $fullClassName;
            } elseif (class_exists($fullClassName . 'Fixture')) {
                $config[] = $fullClassName . 'Fixture';
            }
        }

        return $config;
    }

    /**
     * Filters fixtures by splitting them in two categories: one that should be applied and not.
     *
     * If fixture is prefixed with "-", for example "-User", that means that fixture should not be loaded,
     * if it is not prefixed it is considered as one to be loaded. Returns array:
     *
     * ```php
     * [
     *     'apply' => [
     *         'User',
     *         ...
     *     ],
     *     'except' => [
     *         'Custom',
     *         ...
     *     ],
     * ]
     * ```
     * @param array $fixtures
     * @return array fixtures array with 'apply' and 'except' elements.
     */
    private function filterFixtures($fixtures)
    {
        $filtered = [
            'apply' => [],
            'except' => [],
        ];

        foreach ($fixtures as $fixture) {
            if (mb_strpos($fixture, '-') !== false) {
                $filtered['except'][] = str_replace('-', '', $fixture);
            } else {
                $filtered['apply'][] = $fixture;
            }
        }

        return $filtered;
    }

    /**
     * Returns fixture path that determined on fixtures namespace.
     * @throws InvalidConfigException if fixture namespace is invalid
     * @return string fixture path
     */
    private function getFixturePath()
    {
        try {
            return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace));
        } catch (InvalidParamException $e) {
            throw new InvalidConfigException('Invalid fixture namespace: "' . $this->namespace . '". Please, check your FixtureController::namespace parameter');
        }
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\db\oci\conditions;

use yii\db\ExpressionInterface;

/**
 * {@inheritdoc}
 */
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder
{
    /**
     * {@inheritdoc}
     */
    protected $escapeCharacter = '!';
    /**
     * `\` is initialized in [[buildLikeCondition()]] method since
     * we need to choose replacement value based on [[\yii\db\Schema::quoteValue()]].
     * {@inheritdoc}
     */
    protected $escapingReplacements = [
        '%' => '!%',
        '_' => '!_',
        '!' => '!!',
    ];

    /**
     * {@inheritdoc}
     */
    public function build(ExpressionInterface $expression, array &$params = [])
    {
        if (!isset($this->escapingReplacements['\\'])) {
            /*
             * Different pdo_oci8 versions may or may not implement PDO::quote(), so
             * yii\db\Schema::quoteValue() may or may not quote \.
             */
            $this->escapingReplacements['\\'] = substr($this->queryBuilder->db->quoteValue('\\'), 1, -1);
        }

        return parent::build($expression, $params);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\validators;

use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Json;
use yii\web\JsExpression;

/**
 * UrlValidator validates that the attribute value is a valid http or https URL.
 *
 * Note that this validator only checks if the URL scheme and host part are correct.
 * It does not check the remaining parts of a URL.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class UrlValidator extends Validator
{
    /**
     * @var string the regular expression used to validate the attribute value.
     * The pattern may contain a `{schemes}` token that will be replaced
     * by a regular expression which represents the [[validSchemes]].
     */
    public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(?::\d{1,5})?(?:$|[?\/#])/i';
    /**
     * @var array list of URI schemes which should be considered valid. By default, http and https
     * are considered to be valid schemes.
     */
    public $validSchemes = ['http', 'https'];
    /**
     * @var string the default URI scheme. If the input doesn't contain the scheme part, the default
     * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must
     * contain the scheme part.
     */
    public $defaultScheme;
    /**
     * @var bool whether validation process should take into account IDN (internationalized
     * domain names). Defaults to false meaning that validation of URLs containing IDN will always
     * fail. Note that in order to use IDN validation you have to install and enable `intl` PHP
     * extension, otherwise an exception would be thrown.
     */
    public $enableIDN = false;

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();
        if ($this->enableIDN && !function_exists('idn_to_ascii')) {
            throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
        }
        if ($this->message === null) {
            $this->message = Yii::t('yii', '{attribute} is not a valid URL.');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function validateAttribute($model, $attribute)
    {
        $value = $model->$attribute;
        $result = $this->validateValue($value);
        if (!empty($result)) {
            $this->addError($model, $attribute, $result[0], $result[1]);
        } elseif ($this->defaultScheme !== null && strpos($value, '://') === false) {
            $model->$attribute = $this->defaultScheme . '://' . $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function validateValue($value)
    {
        // make sure the length is limited to avoid DOS attacks
        if (is_string($value) && strlen($value) < 2000) {
            if ($this->defaultScheme !== null && strpos($value, '://') === false) {
                $value = $this->defaultScheme . '://' . $value;
            }

            if (strpos($this->pattern, '{schemes}') !== false) {
                $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
            } else {
                $pattern = $this->pattern;
            }

            if ($this->enableIDN) {
                $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) {
                    return '://' . $this->idnToAscii($matches[1]);
                }, $value);
            }

            if (preg_match($pattern, $value)) {
                return null;
            }
        }

        return [$this->message, []];
    }

    private function idnToAscii($idn)
    {
        if (PHP_VERSION_ID < 50600) {
            // TODO: drop old PHP versions support
            return idn_to_ascii($idn);
        }

        return idn_to_ascii($idn, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
    }

    /**
     * {@inheritdoc}
     */
    public function clientValidateAttribute($model, $attribute, $view)
    {
        ValidationAsset::register($view);
        if ($this->enableIDN) {
            PunycodeAsset::register($view);
        }
        $options = $this->getClientOptions($model, $attribute);

        return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');';
    }

    /**
     * {@inheritdoc}
     */
    public function getClientOptions($model, $attribute)
    {
        if (strpos($this->pattern, '{schemes}') !== false) {
            $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
        } else {
            $pattern = $this->pattern;
        }

        $options = [
            'pattern' => new JsExpression($pattern),
            'message' => $this->formatMessage($this->message, [
                'attribute' => $model->getAttributeLabel($attribute),
            ]),
            'enableIDN' => (bool) $this->enableIDN,
        ];
        if ($this->skipOnEmpty) {
            $options['skipOnEmpty'] = 1;
        }
        if ($this->defaultScheme !== null) {
            $options['defaultScheme'] = $this->defaultScheme;
        }

        return $options;
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\grid;

use Closure;
use yii\base\InvalidConfigException;
use yii\helpers\Html;

/**
 * RadioButtonColumn displays a column of radio buttons in a grid view.
 *
 * To add a RadioButtonColumn to the [[GridView]], add it to the [[GridView::columns|columns]] configuration as follows:
 *
 * ```php
 * 'columns' => [
 *     // ...
 *     [
 *         'class' => 'yii\grid\RadioButtonColumn',
 *         'radioOptions' => function ($model) {
 *              return [
 *                  'value' => $model['value'],
 *                  'checked' => $model['value'] == 2
 *              ];
 *          }
 *     ],
 * ]
 * ```
 *
 * @author Kirk Hansen <hanski07@luther.edu>
 * @since 2.0.11
 */
class RadioButtonColumn extends Column
{
    /**
     * @var string the name of the input radio button input fields.
     */
    public $name = 'radioButtonSelection';
    /**
     * @var array|\Closure the HTML attributes for the radio buttons. This can either be an array of
     * attributes or an anonymous function ([[Closure]]) returning such an array.
     *
     * The signature of the function should be as follows: `function ($model, $key, $index, $column)`
     * where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered
     * and `$column` is a reference to the [[RadioButtonColumn]] object.
     *
     * A function may be used to assign different attributes to different rows based on the data in that row.
     * Specifically if you want to set a different value for the radio button you can use this option
     * in the following way (in this example using the `name` attribute of the model):
     *
     * ```php
     * 'radioOptions' => function ($model, $key, $index, $column) {
     *     return ['value' => $model->attribute];
     * }
     * ```
     *
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $radioOptions = [];

    /**
     * {@inheritdoc}
     * @throws \yii\base\InvalidConfigException if [[name]] is not set.
     */
    public function init()
    {
        parent::init();
        if (empty($this->name)) {
            throw new InvalidConfigException('The "name" property must be set.');
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function renderDataCellContent($model, $key, $index)
    {
        if ($this->content !== null) {
            return parent::renderDataCellContent($model, $key, $index);
        }

        if ($this->radioOptions instanceof Closure) {
            $options = call_user_func($this->radioOptions, $model, $key, $index, $this);
        } else {
            $options = $this->radioOptions;
            if (!isset($options['value'])) {
                $options['value'] = is_array($key) ? json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $key;
            }
        }
        $checked = isset($options['checked']) ? $options['checked'] : false;
        return Html::radio($this->name, $checked, $options);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\web;

use Yii;
use yii\base\BaseObject;

/**
 * HeaderCollection is used by [[Response]] to maintain the currently registered HTTP headers.
 *
 * @property int $count The number of headers in the collection. This property is read-only.
 * @property \ArrayIterator $iterator An iterator for traversing the headers in the collection. This property
 * is read-only.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class HeaderCollection extends BaseObject implements \IteratorAggregate, \ArrayAccess, \Countable
{
    /**
     * @var array the headers in this collection (indexed by the header names)
     */
    private $_headers = [];

    /**
     * Returns an iterator for traversing the headers in the collection.
     * This method is required by the SPL interface [[\IteratorAggregate]].
     * It will be implicitly called when you use `foreach` to traverse the collection.
     * @return \ArrayIterator an iterator for traversing the headers in the collection.
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->_headers);
    }

    /**
     * Returns the number of headers in the collection.
     * This method is required by the SPL `Countable` interface.
     * It will be implicitly called when you use `count($collection)`.
     * @return int the number of headers in the collection.
     */
    public function count()
    {
        return $this->getCount();
    }

    /**
     * Returns the number of headers in the collection.
     * @return int the number of headers in the collection.
     */
    public function getCount()
    {
        return count($this->_headers);
    }

    /**
     * Returns the named header(s).
     * @param string $name the name of the header to return
     * @param mixed $default the value to return in case the named header does not exist
     * @param bool $first whether to only return the first header of the specified name.
     * If false, all headers of the specified name will be returned.
     * @return string|array the named header(s). If `$first` is true, a string will be returned;
     * If `$first` is false, an array will be returned.
     */
    public function get($name, $default = null, $first = true)
    {
        $name = strtolower($name);
        if (isset($this->_headers[$name])) {
            return $first ? reset($this->_headers[$name]) : $this->_headers[$name];
        }

        return $default;
    }

    /**
     * Adds a new header.
     * If there is already a header with the same name, it will be replaced.
     * @param string $name the name of the header
     * @param string $value the value of the header
     * @return $this the collection object itself
     */
    public function set($name, $value = '')
    {
        $name = strtolower($name);
        $this->_headers[$name] = (array) $value;

        return $this;
    }

    /**
     * Adds a new header.
     * If there is already a header with the same name, the new one will
     * be appended to it instead of replacing it.
     * @param string $name the name of the header
     * @param string $value the value of the header
     * @return $this the collection object itself
     */
    public function add($name, $value)
    {
        $name = strtolower($name);
        $this->_headers[$name][] = $value;

        return $this;
    }

    /**
     * Sets a new header only if it does not exist yet.
     * If there is already a header with the same name, the new one will be ignored.
     * @param string $name the name of the header
     * @param string $value the value of the header
     * @return $this the collection object itself
     */
    public function setDefault($name, $value)
    {
        $name = strtolower($name);
        if (empty($this->_headers[$name])) {
            $this->_headers[$name][] = $value;
        }

        return $this;
    }

    /**
     * Returns a value indicating whether the named header exists.
     * @param string $name the name of the header
     * @return bool whether the named header exists
     */
    public function has($name)
    {
        $name = strtolower($name);

        return isset($this->_headers[$name]);
    }

    /**
     * Removes a header.
     * @param string $name the name of the header to be removed.
     * @return array the value of the removed header. Null is returned if the header does not exist.
     */
    public function remove($name)
    {
        $name = strtolower($name);
        if (isset($this->_headers[$name])) {
            $value = $this->_headers[$name];
            unset($this->_headers[$name]);
            return $value;
        }

        return null;
    }

    /**
     * Removes all headers.
     */
    public function removeAll()
    {
        $this->_headers = [];
    }

    /**
     * Returns the collection as a PHP array.
     * @return array the array representation of the collection.
     * The array keys are header names, and the array values are the corresponding header values.
     */
    public function toArray()
    {
        return $this->_headers;
    }

    /**
     * Populates the header collection from an array.
     * @param array $array the headers to populate from
     * @since 2.0.3
     */
    public function fromArray(array $array)
    {
        $this->_headers = $array;
    }

    /**
     * Returns whether there is a header with the specified name.
     * This method is required by the SPL interface [[\ArrayAccess]].
     * It is implicitly called when you use something like `isset($collection[$name])`.
     * @param string $name the header name
     * @return bool whether the named header exists
     */
    public function offsetExists($name)
    {
        return $this->has($name);
    }

    /**
     * Returns the header with the specified name.
     * This method is required by the SPL interface [[\ArrayAccess]].
     * It is implicitly called when you use something like `$header = $collection[$name];`.
     * This is equivalent to [[get()]].
     * @param string $name the header name
     * @return string the header value with the specified name, null if the named header does not exist.
     */
    public function offsetGet($name)
    {
        return $this->get($name);
    }

    /**
     * Adds the header to the collection.
     * This method is required by the SPL interface [[\ArrayAccess]].
     * It is implicitly called when you use something like `$collection[$name] = $header;`.
     * This is equivalent to [[add()]].
     * @param string $name the header name
     * @param string $value the header value to be added
     */
    public function offsetSet($name, $value)
    {
        $this->set($name, $value);
    }

    /**
     * Removes the named header.
     * This method is required by the SPL interface [[\ArrayAccess]].
     * It is implicitly called when you use something like `unset($collection[$name])`.
     * This is equivalent to [[remove()]].
     * @param string $name the header name
     */
    public function offsetUnset($name)
    {
        $this->remove($name);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\filters;

use Yii;
use yii\base\ActionFilter;
use yii\base\BootstrapInterface;
use yii\web\BadRequestHttpException;
use yii\web\Request;
use yii\web\Response;
use yii\web\UnsupportedMediaTypeHttpException;

/**
 * ContentNegotiator supports response format negotiation and application language negotiation.
 *
 * When the [[formats|supported formats]] property is specified, ContentNegotiator will support response format
 * negotiation based on the value of the GET parameter [[formatParam]] and the `Accept` HTTP header.
 * If a match is found, the [[Response::format]] property will be set as the chosen format.
 * The [[Response::acceptMimeType]] as well as [[Response::acceptParams]] will also be updated accordingly.
 *
 * When the [[languages|supported languages]] is specified, ContentNegotiator will support application
 * language negotiation based on the value of the GET parameter [[languageParam]] and the `Accept-Language` HTTP header.
 * If a match is found, the [[\yii\base\Application::language]] property will be set as the chosen language.
 *
 * You may use ContentNegotiator as a bootstrapping component as well as an action filter.
 *
 * The following code shows how you can use ContentNegotiator as a bootstrapping component. Note that in this case,
 * the content negotiation applies to the whole application.
 *
 * ```php
 * // in application configuration
 * use yii\web\Response;
 *
 * return [
 *     'bootstrap' => [
 *         [
 *             'class' => 'yii\filters\ContentNegotiator',
 *             'formats' => [
 *                 'application/json' => Response::FORMAT_JSON,
 *                 'application/xml' => Response::FORMAT_XML,
 *             ],
 *             'languages' => [
 *                 'en',
 *                 'de',
 *             ],
 *         ],
 *     ],
 * ];
 * ```
 *
 * The following code shows how you can use ContentNegotiator as an action filter in either a controller or a module.
 * In this case, the content negotiation result only applies to the corresponding controller or module, or even
 * specific actions if you configure the `only` or `except` property of the filter.
 *
 * ```php
 * use yii\web\Response;
 *
 * public function behaviors()
 * {
 *     return [
 *         [
 *             'class' => 'yii\filters\ContentNegotiator',
 *             'only' => ['view', 'index'],  // in a controller
 *             // if in a module, use the following IDs for user actions
 *             // 'only' => ['user/view', 'user/index']
 *             'formats' => [
 *                 'application/json' => Response::FORMAT_JSON,
 *             ],
 *             'languages' => [
 *                 'en',
 *                 'de',
 *             ],
 *         ],
 *     ];
 * }
 * ```
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class ContentNegotiator extends ActionFilter implements BootstrapInterface
{
    /**
     * @var string the name of the GET parameter that specifies the response format.
     * Note that if the specified format does not exist in [[formats]], a [[UnsupportedMediaTypeHttpException]]
     * exception will be thrown.  If the parameter value is empty or if this property is null,
     * the response format will be determined based on the `Accept` HTTP header only.
     * @see formats
     */
    public $formatParam = '_format';
    /**
     * @var string the name of the GET parameter that specifies the [[\yii\base\Application::language|application language]].
     * Note that if the specified language does not match any of [[languages]], the first language in [[languages]]
     * will be used. If the parameter value is empty or if this property is null,
     * the application language will be determined based on the `Accept-Language` HTTP header only.
     * @see languages
     */
    public $languageParam = '_lang';
    /**
     * @var array list of supported response formats. The keys are MIME types (e.g. `application/json`)
     * while the values are the corresponding formats (e.g. `html`, `json`) which must be supported
     * as declared in [[\yii\web\Response::formatters]].
     *
     * If this property is empty or not set, response format negotiation will be skipped.
     */
    public $formats;
    /**
     * @var array a list of supported languages. The array keys are the supported language variants (e.g. `en-GB`, `en-US`),
     * while the array values are the corresponding language codes (e.g. `en`, `de`) recognized by the application.
     *
     * Array keys are not always required. When an array value does not have a key, the matching of the requested language
     * will be based on a language fallback mechanism. For example, a value of `en` will match `en`, `en_US`, `en-US`, `en-GB`, etc.
     *
     * If this property is empty or not set, language negotiation will be skipped.
     */
    public $languages;
    /**
     * @var Request the current request. If not set, the `request` application component will be used.
     */
    public $request;
    /**
     * @var Response the response to be sent. If not set, the `response` application component will be used.
     */
    public $response;

    /**
     * {@inheritdoc}
     */
    public function bootstrap($app)
    {
        $this->negotiate();
    }

    /**
     * {@inheritdoc}
     */
    public function beforeAction($action)
    {
        $this->negotiate();
        return true;
    }

    /**
     * Negotiates the response format and application language.
     */
    public function negotiate()
    {
        $request = $this->request ?: Yii::$app->getRequest();
        $response = $this->response ?: Yii::$app->getResponse();
        if (!empty($this->formats)) {
            if (\count($this->formats) > 1) {
                $response->getHeaders()->add('Vary', 'Accept');
            }
            $this->negotiateContentType($request, $response);
        }
        if (!empty($this->languages)) {
            if (\count($this->languages) > 1) {
                $response->getHeaders()->add('Vary', 'Accept-Language');
            }
            Yii::$app->language = $this->negotiateLanguage($request);
        }
    }

    /**
     * Negotiates the response format.
     * @param Request $request
     * @param Response $response
     * @throws BadRequestHttpException if an array received for GET parameter [[formatParam]].
     * @throws UnsupportedMediaTypeHttpException if none of the requested content types is accepted.
     */
    protected function negotiateContentType($request, $response)
    {
        if (!empty($this->formatParam) && ($format = $request->get($this->formatParam)) !== null) {
            if (is_array($format)) {
                throw new BadRequestHttpException("Invalid data received for GET parameter '{$this->formatParam}'.");
            }

            if (in_array($format, $this->formats)) {
                $response->format = $format;
                $response->acceptMimeType = null;
                $response->acceptParams = [];
                return;
            }

            throw new UnsupportedMediaTypeHttpException('The requested response format is not supported: ' . $format);
        }

        $types = $request->getAcceptableContentTypes();
        if (empty($types)) {
            $types['*/*'] = [];
        }

        foreach ($types as $type => $params) {
            if (isset($this->formats[$type])) {
                $response->format = $this->formats[$type];
                $response->acceptMimeType = $type;
                $response->acceptParams = $params;
                return;
            }
        }

        foreach ($this->formats as $type => $format) {
            $response->format = $format;
            $response->acceptMimeType = $type;
            $response->acceptParams = [];
            break;
        }

        if (isset($types['*/*'])) {
            return;
        }

        throw new UnsupportedMediaTypeHttpException('None of your requested content types is supported.');
    }

    /**
     * Negotiates the application language.
     * @param Request $request
     * @return string the chosen language
     */
    protected function negotiateLanguage($request)
    {
        if (!empty($this->languageParam) && ($language = $request->get($this->languageParam)) !== null) {
            if (is_array($language)) {
                // If an array received, then skip it and use the first of supported languages
                return reset($this->languages);
            }
            if (isset($this->languages[$language])) {
                return $this->languages[$language];
            }
            foreach ($this->languages as $key => $supported) {
                if (is_int($key) && $this->isLanguageSupported($language, $supported)) {
                    return $supported;
                }
            }

            return reset($this->languages);
        }

        foreach ($request->getAcceptableLanguages() as $language) {
            if (isset($this->languages[$language])) {
                return $this->languages[$language];
            }
            foreach ($this->languages as $key => $supported) {
                if (is_int($key) && $this->isLanguageSupported($language, $supported)) {
                    return $supported;
                }
            }
        }

        return reset($this->languages);
    }

    /**
     * Returns a value indicating whether the requested language matches the supported language.
     * @param string $requested the requested language code
     * @param string $supported the supported language code
     * @return bool whether the requested language is supported
     */
    protected function isLanguageSupported($requested, $supported)
    {
        $supported = str_replace('_', '-', strtolower($supported));
        $requested = str_replace('_', '-', strtolower($requested));
        return strpos($requested . '-', $supported . '-') === 0;
    }
}