kevin_zhangl 3 лет назад
Родитель
Сommit
78296d0498
68 измененных файлов с 13431 добавлено и 2 удалено
  1. 93 1
      .gitignore
  2. 6 0
      common/config/params.php
  3. 48 0
      common/helpers/DingTalk.php
  4. 1 1
      common/models/forms/ApproachOrderForm.php
  5. 204 0
      vendor/yiisoft/yii2/web/Application.php
  6. 211 0
      vendor/yiisoft/yii2/web/AssetBundle.php
  7. 120 0
      vendor/yiisoft/yii2/web/AssetConverter.php
  8. 25 0
      vendor/yiisoft/yii2/web/AssetConverterInterface.php
  9. 669 0
      vendor/yiisoft/yii2/web/AssetManager.php
  10. 34 0
      vendor/yiisoft/yii2/web/BadRequestHttpException.php
  11. 147 0
      vendor/yiisoft/yii2/web/CacheSession.php
  12. 143 0
      vendor/yiisoft/yii2/web/CompositeUrlRule.php
  13. 29 0
      vendor/yiisoft/yii2/web/ConflictHttpException.php
  14. 318 0
      vendor/yiisoft/yii2/web/Controller.php
  15. 103 0
      vendor/yiisoft/yii2/web/Cookie.php
  16. 244 0
      vendor/yiisoft/yii2/web/CookieCollection.php
  17. 295 0
      vendor/yiisoft/yii2/web/DbSession.php
  18. 221 0
      vendor/yiisoft/yii2/web/ErrorAction.php
  19. 529 0
      vendor/yiisoft/yii2/web/ErrorHandler.php
  20. 34 0
      vendor/yiisoft/yii2/web/ForbiddenHttpException.php
  21. 34 0
      vendor/yiisoft/yii2/web/GoneHttpException.php
  22. 144 0
      vendor/yiisoft/yii2/web/GroupUrlRule.php
  23. 235 0
      vendor/yiisoft/yii2/web/HeaderCollection.php
  24. 29 0
      vendor/yiisoft/yii2/web/HeadersAlreadySentException.php
  25. 42 0
      vendor/yiisoft/yii2/web/HtmlResponseFormatter.php
  26. 62 0
      vendor/yiisoft/yii2/web/HttpException.php
  27. 110 0
      vendor/yiisoft/yii2/web/IdentityInterface.php
  28. 22 0
      vendor/yiisoft/yii2/web/JqueryAsset.php
  29. 48 0
      vendor/yiisoft/yii2/web/JsExpression.php
  30. 66 0
      vendor/yiisoft/yii2/web/JsonParser.php
  31. 147 0
      vendor/yiisoft/yii2/web/JsonResponseFormatter.php
  32. 76 0
      vendor/yiisoft/yii2/web/Link.php
  33. 42 0
      vendor/yiisoft/yii2/web/Linkable.php
  34. 29 0
      vendor/yiisoft/yii2/web/MethodNotAllowedHttpException.php
  35. 131 0
      vendor/yiisoft/yii2/web/MultiFieldSession.php
  36. 372 0
      vendor/yiisoft/yii2/web/MultipartFormDataParser.php
  37. 33 0
      vendor/yiisoft/yii2/web/NotAcceptableHttpException.php
  38. 29 0
      vendor/yiisoft/yii2/web/NotFoundHttpException.php
  39. 35 0
      vendor/yiisoft/yii2/web/RangeNotSatisfiableHttpException.php
  40. 1957 0
      vendor/yiisoft/yii2/web/Request.php
  41. 25 0
      vendor/yiisoft/yii2/web/RequestParserInterface.php
  42. 1138 0
      vendor/yiisoft/yii2/web/Response.php
  43. 23 0
      vendor/yiisoft/yii2/web/ResponseFormatterInterface.php
  44. 29 0
      vendor/yiisoft/yii2/web/ServerErrorHttpException.php
  45. 1069 0
      vendor/yiisoft/yii2/web/Session.php
  46. 85 0
      vendor/yiisoft/yii2/web/SessionIterator.php
  47. 33 0
      vendor/yiisoft/yii2/web/TooManyRequestsHttpException.php
  48. 36 0
      vendor/yiisoft/yii2/web/UnauthorizedHttpException.php
  49. 35 0
      vendor/yiisoft/yii2/web/UnprocessableEntityHttpException.php
  50. 34 0
      vendor/yiisoft/yii2/web/UnsupportedMediaTypeHttpException.php
  51. 284 0
      vendor/yiisoft/yii2/web/UploadedFile.php
  52. 663 0
      vendor/yiisoft/yii2/web/UrlManager.php
  53. 150 0
      vendor/yiisoft/yii2/web/UrlNormalizer.php
  54. 52 0
      vendor/yiisoft/yii2/web/UrlNormalizerRedirectException.php
  55. 602 0
      vendor/yiisoft/yii2/web/UrlRule.php
  56. 35 0
      vendor/yiisoft/yii2/web/UrlRuleInterface.php
  57. 813 0
      vendor/yiisoft/yii2/web/User.php
  58. 40 0
      vendor/yiisoft/yii2/web/UserEvent.php
  59. 673 0
      vendor/yiisoft/yii2/web/View.php
  60. 131 0
      vendor/yiisoft/yii2/web/ViewAction.php
  61. 185 0
      vendor/yiisoft/yii2/web/XmlResponseFormatter.php
  62. 25 0
      vendor/yiisoft/yii2/web/YiiAsset.php
  63. 53 0
      vendor/yiisoft/yii2/web/migrations/m160313_153426_session_init.php
  64. 21 0
      vendor/yiisoft/yii2/web/migrations/schema-mssql.sql
  65. 20 0
      vendor/yiisoft/yii2/web/migrations/schema-mysql.sql
  66. 20 0
      vendor/yiisoft/yii2/web/migrations/schema-oci.sql
  67. 20 0
      vendor/yiisoft/yii2/web/migrations/schema-pgsql.sql
  68. 20 0
      vendor/yiisoft/yii2/web/migrations/schema-sqlite.sql

+ 93 - 1
.gitignore

@@ -20,9 +20,101 @@ Desktop.ini
 #App
 common/config/config.php
 common/config/config-product.php
-vendor
 frontendApi/web/pdfs/
 #composer.json
 composer.lock
 backendApi/web/uploads
 frontendApi/web/uploads
+.fleet/settings.json
+vendor/anlity/
+vendor/behat/
+vendor/bin/
+vendor/bower-asset/
+vendor/cebe/
+vendor/codeception/
+vendor/composer/
+vendor/doctrine/
+vendor/egulias/
+vendor/ezyang/
+vendor/fakerphp/
+vendor/godruoyi/
+vendor/guzzlehttp/
+vendor/maennchen/
+vendor/markbaker/
+vendor/monolog/
+vendor/myclabs/
+vendor/paragonie/
+vendor/phar-io/
+vendor/phpdocumentor/
+vendor/phpoffice/
+vendor/phpspec/
+vendor/phpunit/
+vendor/psr/
+vendor/sebastian/
+vendor/smladeoye/
+vendor/ralouphie/
+vendor/swiftmailer/
+vendor/symfony/
+vendor/tecnickcom/
+vendor/theseer/
+vendor/webmozart/
+vendor/sunmoon/
+vendor/yiisoft/yii2-swiftmailer/
+vendor/yiisoft/yii2-mongodb/
+vendor/yiisoft/yii2-redis/
+vendor/yiisoft/yii2-gii/
+vendor/yiisoft/yii2-httpclient/
+vendor/yiisoft/yii2-debug/
+vendor/yiisoft/yii2-bootstrap/
+vendor/yiisoft/yii2/widgets/
+vendor/yiisoft/yii2-faker/
+vendor/yiisoft/yii2-composer/
+vendor/yiisoft/yii2/views/
+vendor/yiisoft/yii2/validators/
+vendor/yiisoft/yii2/rest/
+vendor/yiisoft/yii2/requirements/
+vendor/yiisoft/yii2/rbac/
+vendor/yiisoft/yii2/test/
+vendor/yiisoft/yii2/messages/
+vendor/yiisoft/yii2/log/
+vendor/yiisoft/yii2/i18n/
+vendor/yiisoft/yii2/mutex/
+vendor/yiisoft/yii2/helpers/
+vendor/yiisoft/yii2/mail/
+vendor/yiisoft/yii2/filters/
+vendor/yiisoft/yii2/grid/
+vendor/yiisoft/yii2/db/
+vendor/yiisoft/yii2/console/
+vendor/yiisoft/yii2/base/
+vendor/yiisoft/yii2/behaviors/
+vendor/yiisoft/yii2/caching/
+vendor/yiisoft/yii2/data/
+vendor/yiisoft/yii2/captcha/
+vendor/autoload.php
+vendor/yiisoft/extensions.php
+vendor/yiisoft/yii2/.gitignore
+vendor/yiisoft/yii2/.htaccess
+vendor/yiisoft/yii2/.phpstorm.meta.php
+vendor/yiisoft/yii2/BaseYii.php
+vendor/yiisoft/yii2/CHANGELOG.md
+vendor/yiisoft/yii2/classes.php
+vendor/yiisoft/yii2/composer.json
+vendor/yiisoft/yii2/LICENSE.md
+vendor/yiisoft/yii2/README.md
+vendor/yiisoft/yii2/UPGRADE.md
+vendor/yiisoft/yii2/yii
+vendor/yiisoft/yii2/yii.bat
+vendor/yiisoft/yii2/Yii.php
+vendor/yiisoft/yii2/.github/CONTRIBUTING.md
+vendor/yiisoft/yii2/.github/FUNDING.yml
+vendor/yiisoft/yii2/.github/PULL_REQUEST_TEMPLATE.md
+vendor/yiisoft/yii2/.github/SECURITY.md
+vendor/yiisoft/yii2/assets/yii.activeForm.js
+vendor/yiisoft/yii2/assets/yii.captcha.js
+vendor/yiisoft/yii2/assets/yii.gridView.js
+vendor/yiisoft/yii2/assets/yii.js
+vendor/yiisoft/yii2/assets/yii.validation.js
+vendor/yiisoft/yii2/di/Container.php
+vendor/yiisoft/yii2/di/Instance.php
+vendor/yiisoft/yii2/di/NotInstantiableException.php
+vendor/yiisoft/yii2/di/ServiceLocator.php

+ 6 - 0
common/config/params.php

@@ -352,4 +352,10 @@ return [
         'dataCenterId' => 2,//
         'workerId' => 2,
     ],
+    // 钉钉消息
+    'DingTalk' => [
+        'accessToken' => 'b5cc33c4dd000da86c19dd123bd4340e38a19bde5442655468f3d959cb677eb7',
+        'atMobiles'   => ['18511880790'],
+        'isAtAll'     => false,
+    ],
 ];

+ 48 - 0
common/helpers/DingTalk.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace common\helpers;
+
+class DingTalk
+{
+    private static $webhook = 'https://oapi.dingtalk.com/robot/send?access_token=';
+
+    private static function request_by_curl($post_string)
+    {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, self::$webhook . \Yii::$app->params['DingTalk']['accessToken']);
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, array ('Content-Type: application/json;charset=utf-8'));
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        // 线下环境不用开启curl证书验证, 未调通情况可尝试添加该代码
+         curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
+         curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
+        $data = curl_exec($ch);
+        curl_close($ch);
+
+        return $data;
+    }
+
+    public static function sendNotice($message)
+    {
+        $data = [
+            'msgtype' => 'text',
+            'text' => ['content' => $message],
+            "at" => [
+                'atMobiles' => \Yii::$app->params['DingTalk']['atMobiles'],
+                'isAtAll'   => false,
+            ]
+        ];
+
+        $result = self::request_by_curl(json_encode($data));
+
+        if ($result['errcode'] > 0) {
+            // 重新推送一次,如果失败,写错误日志
+            $result = self::request_by_curl(json_encode($data));
+            if (!$result['errcode']) {
+                LoggerTool::error([$result, $message]);
+            }
+        }
+    }
+}

+ 1 - 1
common/models/forms/ApproachOrderForm.php

@@ -430,7 +430,7 @@ class ApproachOrderForm extends Model
                     $this->_orderGoods[] = [
                         'GOODS_ID' => $goods['ID'],
                         'PRICE' => $goods['SELL_PRICE'],
-                        'PV' => $goods['PRICE_PV'],
+                        'PV' => $goods['PRICE_P'],
                         'REAL_PRICE' => $realPrice,
                         'REAL_PV' => $realPv,
                         'POINT' => $goods['POINT'],

+ 204 - 0
vendor/yiisoft/yii2/web/Application.php

@@ -0,0 +1,204 @@
+<?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\InvalidRouteException;
+use yii\helpers\Url;
+
+/**
+ * Application is the base class for all web application classes.
+ *
+ * For more details and usage information on Application, see the [guide article on applications](guide:structure-applications).
+ *
+ * @property-read ErrorHandler $errorHandler The error handler application component. This property is
+ * read-only.
+ * @property string $homeUrl The homepage URL.
+ * @property-read Request $request The request component. This property is read-only.
+ * @property-read Response $response The response component. This property is read-only.
+ * @property-read Session $session The session component. This property is read-only.
+ * @property-read User $user The user component. This property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Application extends \yii\base\Application
+{
+    /**
+     * @var string the default route of this application. Defaults to 'site'.
+     */
+    public $defaultRoute = 'site';
+    /**
+     * @var array the configuration specifying a controller action which should handle
+     * all user requests. This is mainly used when the application is in maintenance mode
+     * and needs to handle all incoming requests via a single action.
+     * The configuration is an array whose first element specifies the route of the action.
+     * The rest of the array elements (key-value pairs) specify the parameters to be bound
+     * to the action. For example,
+     *
+     * ```php
+     * [
+     *     'offline/notice',
+     *     'param1' => 'value1',
+     *     'param2' => 'value2',
+     * ]
+     * ```
+     *
+     * Defaults to null, meaning catch-all is not used.
+     */
+    public $catchAll;
+    /**
+     * @var Controller the currently active controller instance
+     */
+    public $controller;
+
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function bootstrap()
+    {
+        $request = $this->getRequest();
+        Yii::setAlias('@webroot', dirname($request->getScriptFile()));
+        Yii::setAlias('@web', $request->getBaseUrl());
+
+        parent::bootstrap();
+    }
+
+    /**
+     * Handles the specified request.
+     * @param Request $request the request to be handled
+     * @return Response the resulting response
+     * @throws NotFoundHttpException if the requested route is invalid
+     */
+    public function handleRequest($request)
+    {
+        if (empty($this->catchAll)) {
+            try {
+                list($route, $params) = $request->resolve();
+            } catch (UrlNormalizerRedirectException $e) {
+                $url = $e->url;
+                if (is_array($url)) {
+                    if (isset($url[0])) {
+                        // ensure the route is absolute
+                        $url[0] = '/' . ltrim($url[0], '/');
+                    }
+                    $url += $request->getQueryParams();
+                }
+
+                return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
+            }
+        } else {
+            $route = $this->catchAll[0];
+            $params = $this->catchAll;
+            unset($params[0]);
+        }
+        try {
+            Yii::debug("Route requested: '$route'", __METHOD__);
+            $this->requestedRoute = $route;
+            $result = $this->runAction($route, $params);
+            if ($result instanceof Response) {
+                return $result;
+            }
+
+            $response = $this->getResponse();
+            if ($result !== null) {
+                $response->data = $result;
+            }
+
+            return $response;
+        } catch (InvalidRouteException $e) {
+            throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
+        }
+    }
+
+    private $_homeUrl;
+
+    /**
+     * @return string the homepage URL
+     */
+    public function getHomeUrl()
+    {
+        if ($this->_homeUrl === null) {
+            if ($this->getUrlManager()->showScriptName) {
+                return $this->getRequest()->getScriptUrl();
+            }
+
+            return $this->getRequest()->getBaseUrl() . '/';
+        }
+
+        return $this->_homeUrl;
+    }
+
+    /**
+     * @param string $value the homepage URL
+     */
+    public function setHomeUrl($value)
+    {
+        $this->_homeUrl = $value;
+    }
+
+    /**
+     * Returns the error handler component.
+     * @return ErrorHandler the error handler application component.
+     */
+    public function getErrorHandler()
+    {
+        return $this->get('errorHandler');
+    }
+
+    /**
+     * Returns the request component.
+     * @return Request the request component.
+     */
+    public function getRequest()
+    {
+        return $this->get('request');
+    }
+
+    /**
+     * Returns the response component.
+     * @return Response the response component.
+     */
+    public function getResponse()
+    {
+        return $this->get('response');
+    }
+
+    /**
+     * Returns the session component.
+     * @return Session the session component.
+     */
+    public function getSession()
+    {
+        return $this->get('session');
+    }
+
+    /**
+     * Returns the user component.
+     * @return User the user component.
+     */
+    public function getUser()
+    {
+        return $this->get('user');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function coreComponents()
+    {
+        return array_merge(parent::coreComponents(), [
+            'request' => ['class' => 'yii\web\Request'],
+            'response' => ['class' => 'yii\web\Response'],
+            'session' => ['class' => 'yii\web\Session'],
+            'user' => ['class' => 'yii\web\User'],
+            'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
+        ]);
+    }
+}

+ 211 - 0
vendor/yiisoft/yii2/web/AssetBundle.php

@@ -0,0 +1,211 @@
+<?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\helpers\ArrayHelper;
+use yii\helpers\Url;
+
+/**
+ * AssetBundle represents a collection of asset files, such as CSS, JS, images.
+ *
+ * Each asset bundle has a unique name that globally identifies it among all asset bundles used in an application.
+ * The name is the [fully qualified class name](https://secure.php.net/manual/en/language.namespaces.rules.php)
+ * of the class representing it.
+ *
+ * An asset bundle can depend on other asset bundles. When registering an asset bundle
+ * with a view, all its dependent asset bundles will be automatically registered.
+ *
+ * For more details and usage information on AssetBundle, see the [guide article on assets](guide:structure-assets).
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AssetBundle extends BaseObject
+{
+    /**
+     * @var string the directory that contains the source asset files for this asset bundle.
+     * A source asset file is a file that is part of your source code repository of your Web application.
+     *
+     * You must set this property if the directory containing the source asset files is not Web accessible.
+     * By setting this property, [[AssetManager]] will publish the source asset files
+     * to a Web-accessible directory automatically when the asset bundle is registered on a page.
+     *
+     * If you do not set this property, it means the source asset files are located under [[basePath]].
+     *
+     * You can use either a directory or an alias of the directory.
+     * @see $publishOptions
+     */
+    public $sourcePath;
+    /**
+     * @var string the Web-accessible directory that contains the asset files in this bundle.
+     *
+     * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
+     * when it publishes the asset files from [[sourcePath]].
+     *
+     * You can use either a directory or an alias of the directory.
+     */
+    public $basePath;
+    /**
+     * @var string the base URL for the relative asset files listed in [[js]] and [[css]].
+     *
+     * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
+     * when it publishes the asset files from [[sourcePath]].
+     *
+     * You can use either a URL or an alias of the URL.
+     */
+    public $baseUrl;
+    /**
+     * @var array list of bundle class names that this bundle depends on.
+     *
+     * For example:
+     *
+     * ```php
+     * public $depends = [
+     *    'yii\web\YiiAsset',
+     *    'yii\bootstrap\BootstrapAsset',
+     * ];
+     * ```
+     */
+    public $depends = [];
+    /**
+     * @var array list of JavaScript files that this bundle contains. Each JavaScript file can be
+     * specified in one of the following formats:
+     *
+     * - an absolute URL representing an external asset. For example,
+     *   `http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js` or
+     *   `//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js`.
+     * - a relative path representing a local asset (e.g. `js/main.js`). The actual file path of a local
+     *   asset can be determined by prefixing [[basePath]] to the relative path, and the actual URL
+     *   of the asset can be determined by prefixing [[baseUrl]] to the relative path.
+     * - an array, with the first entry being the URL or relative path as described before, and a list of key => value pairs
+     *   that will be used to overwrite [[jsOptions]] settings for this entry.
+     *   This functionality is available since version 2.0.7.
+     *
+     * Note that only a forward slash "/" should be used as directory separator.
+     */
+    public $js = [];
+    /**
+     * @var array list of CSS files that this bundle contains. Each CSS file can be specified
+     * in one of the three formats as explained in [[js]].
+     *
+     * Note that only a forward slash "/" should be used as directory separator.
+     */
+    public $css = [];
+    /**
+     * @var array the options that will be passed to [[View::registerJsFile()]]
+     * when registering the JS files in this bundle.
+     */
+    public $jsOptions = [];
+    /**
+     * @var array the options that will be passed to [[View::registerCssFile()]]
+     * when registering the CSS files in this bundle.
+     */
+    public $cssOptions = [];
+    /**
+     * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle
+     * is being published. This property is used only when [[sourcePath]] is set.
+     */
+    public $publishOptions = [];
+
+
+    /**
+     * Registers this asset bundle with a view.
+     * @param View $view the view to be registered with
+     * @return static the registered asset bundle instance
+     */
+    public static function register($view)
+    {
+        return $view->registerAssetBundle(get_called_class());
+    }
+
+    /**
+     * Initializes the bundle.
+     * If you override this method, make sure you call the parent implementation in the last.
+     */
+    public function init()
+    {
+        if ($this->sourcePath !== null) {
+            $this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\');
+        }
+        if ($this->basePath !== null) {
+            $this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\');
+        }
+        if ($this->baseUrl !== null) {
+            $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
+        }
+    }
+
+    /**
+     * Registers the CSS and JS files with the given view.
+     * @param \yii\web\View $view the view that the asset files are to be registered with.
+     */
+    public function registerAssetFiles($view)
+    {
+        $manager = $view->getAssetManager();
+        foreach ($this->js as $js) {
+            if (is_array($js)) {
+                $file = array_shift($js);
+                $options = ArrayHelper::merge($this->jsOptions, $js);
+                $view->registerJsFile($manager->getAssetUrl($this, $file, ArrayHelper::getValue($options, 'appendTimestamp')), $options);
+            } elseif ($js !== null) {
+                $view->registerJsFile($manager->getAssetUrl($this, $js), $this->jsOptions);
+            }
+        }
+        foreach ($this->css as $css) {
+            if (is_array($css)) {
+                $file = array_shift($css);
+                $options = ArrayHelper::merge($this->cssOptions, $css);
+                $view->registerCssFile($manager->getAssetUrl($this, $file, ArrayHelper::getValue($options, 'appendTimestamp')), $options);
+            } elseif ($css !== null) {
+                $view->registerCssFile($manager->getAssetUrl($this, $css), $this->cssOptions);
+            }
+        }
+    }
+
+    /**
+     * Publishes the asset bundle if its source code is not under Web-accessible directory.
+     * It will also try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding
+     * CSS or JS files using [[AssetManager::converter|asset converter]].
+     * @param AssetManager $am the asset manager to perform the asset publishing
+     */
+    public function publish($am)
+    {
+        if ($this->sourcePath !== null && !isset($this->basePath, $this->baseUrl)) {
+            list($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions);
+        }
+
+        if (isset($this->basePath, $this->baseUrl) && ($converter = $am->getConverter()) !== null) {
+            foreach ($this->js as $i => $js) {
+                if (is_array($js)) {
+                    $file = array_shift($js);
+                    if (Url::isRelative($file)) {
+                        $js = ArrayHelper::merge($this->jsOptions, $js);
+                        array_unshift($js, $converter->convert($file, $this->basePath));
+                        $this->js[$i] = $js;
+                    }
+                } elseif (Url::isRelative($js)) {
+                    $this->js[$i] = $converter->convert($js, $this->basePath);
+                }
+            }
+            foreach ($this->css as $i => $css) {
+                if (is_array($css)) {
+                    $file = array_shift($css);
+                    if (Url::isRelative($file)) {
+                        $css = ArrayHelper::merge($this->cssOptions, $css);
+                        array_unshift($css, $converter->convert($file, $this->basePath));
+                        $this->css[$i] = $css;
+                    }
+                } elseif (Url::isRelative($css)) {
+                    $this->css[$i] = $converter->convert($css, $this->basePath);
+                }
+            }
+        }
+    }
+}

+ 120 - 0
vendor/yiisoft/yii2/web/AssetConverter.php

@@ -0,0 +1,120 @@
+<?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\Component;
+use yii\base\Exception;
+
+/**
+ * AssetConverter supports conversion of several popular script formats into JS or CSS scripts.
+ *
+ * It is used by [[AssetManager]] to convert files after they have been published.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AssetConverter extends Component implements AssetConverterInterface
+{
+    /**
+     * @var array the commands that are used to perform the asset conversion.
+     * The keys are the asset file extension names, and the values are the corresponding
+     * target script types (either "css" or "js") and the commands used for the conversion.
+     *
+     * You may also use a [path alias](guide:concept-aliases) to specify the location of the command:
+     *
+     * ```php
+     * [
+     *     'styl' => ['css', '@app/node_modules/bin/stylus < {from} > {to}'],
+     * ]
+     * ```
+     */
+    public $commands = [
+        'less' => ['css', 'lessc {from} {to} --no-color --source-map'],
+        'scss' => ['css', 'sass {from} {to} --sourcemap'],
+        'sass' => ['css', 'sass {from} {to} --sourcemap'],
+        'styl' => ['css', 'stylus < {from} > {to}'],
+        'coffee' => ['js', 'coffee -p {from} > {to}'],
+        'ts' => ['js', 'tsc --out {to} {from}'],
+    ];
+    /**
+     * @var bool whether the source asset file should be converted even if its result already exists.
+     * You may want to set this to be `true` during the development stage to make sure the converted
+     * assets are always up-to-date. Do not set this to true on production servers as it will
+     * significantly degrade the performance.
+     */
+    public $forceConvert = false;
+
+
+    /**
+     * Converts a given asset file into a CSS or JS file.
+     * @param string $asset the asset file path, relative to $basePath
+     * @param string $basePath the directory the $asset is relative to.
+     * @return string the converted asset file path, relative to $basePath.
+     */
+    public function convert($asset, $basePath)
+    {
+        $pos = strrpos($asset, '.');
+        if ($pos !== false) {
+            $ext = substr($asset, $pos + 1);
+            if (isset($this->commands[$ext])) {
+                list($ext, $command) = $this->commands[$ext];
+                $result = substr($asset, 0, $pos + 1) . $ext;
+                if ($this->forceConvert || @filemtime("$basePath/$result") < @filemtime("$basePath/$asset")) {
+                    $this->runCommand($command, $basePath, $asset, $result);
+                }
+
+                return $result;
+            }
+        }
+
+        return $asset;
+    }
+
+    /**
+     * Runs a command to convert asset files.
+     * @param string $command the command to run. If prefixed with an `@` it will be treated as a [path alias](guide:concept-aliases).
+     * @param string $basePath asset base path and command working directory
+     * @param string $asset the name of the asset file
+     * @param string $result the name of the file to be generated by the converter command
+     * @return bool true on success, false on failure. Failures will be logged.
+     * @throws \yii\base\Exception when the command fails and YII_DEBUG is true.
+     * In production mode the error will be logged.
+     */
+    protected function runCommand($command, $basePath, $asset, $result)
+    {
+        $command = Yii::getAlias($command);
+
+        $command = strtr($command, [
+            '{from}' => escapeshellarg("$basePath/$asset"),
+            '{to}' => escapeshellarg("$basePath/$result"),
+        ]);
+        $descriptor = [
+            1 => ['pipe', 'w'],
+            2 => ['pipe', 'w'],
+        ];
+        $pipes = [];
+        $proc = proc_open($command, $descriptor, $pipes, $basePath);
+        $stdout = stream_get_contents($pipes[1]);
+        $stderr = stream_get_contents($pipes[2]);
+        foreach ($pipes as $pipe) {
+            fclose($pipe);
+        }
+        $status = proc_close($proc);
+
+        if ($status === 0) {
+            Yii::debug("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
+        } elseif (YII_DEBUG) {
+            throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
+        } else {
+            Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
+        }
+
+        return $status === 0;
+    }
+}

+ 25 - 0
vendor/yiisoft/yii2/web/AssetConverterInterface.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * The AssetConverterInterface must be implemented by asset converter classes.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+interface AssetConverterInterface
+{
+    /**
+     * Converts a given asset file into a CSS or JS file.
+     * @param string $asset the asset file path, relative to $basePath
+     * @param string $basePath the directory the $asset is relative to.
+     * @return string the converted asset file path, relative to $basePath.
+     */
+    public function convert($asset, $basePath);
+}

+ 669 - 0
vendor/yiisoft/yii2/web/AssetManager.php

@@ -0,0 +1,669 @@
+<?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\Component;
+use yii\base\InvalidArgumentException;
+use yii\base\InvalidConfigException;
+use yii\helpers\FileHelper;
+use yii\helpers\Url;
+
+/**
+ * AssetManager manages asset bundle configuration and loading.
+ *
+ * AssetManager is configured as an application component in [[\yii\web\Application]] by default.
+ * You can access that instance via `Yii::$app->assetManager`.
+ *
+ * You can modify its configuration by adding an array to your application config under `components`
+ * as shown in the following example:
+ *
+ * ```php
+ * 'assetManager' => [
+ *     'bundles' => [
+ *         // you can override AssetBundle configs here
+ *     ],
+ * ]
+ * ```
+ *
+ * For more details and usage information on AssetManager, see the [guide article on assets](guide:structure-assets).
+ *
+ * @property AssetConverterInterface $converter The asset converter. Note that the type of this property
+ * differs in getter and setter. See [[getConverter()]] and [[setConverter()]] for details.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AssetManager extends Component
+{
+    /**
+     * @var array|bool list of asset bundle configurations. This property is provided to customize asset bundles.
+     * When a bundle is being loaded by [[getBundle()]], if it has a corresponding configuration specified here,
+     * the configuration will be applied to the bundle.
+     *
+     * The array keys are the asset bundle names, which typically are asset bundle class names without leading backslash.
+     * The array values are the corresponding configurations. If a value is false, it means the corresponding asset
+     * bundle is disabled and [[getBundle()]] should return null.
+     *
+     * If this property is false, it means the whole asset bundle feature is disabled and [[getBundle()]]
+     * will always return null.
+     *
+     * The following example shows how to disable the bootstrap css file used by Bootstrap widgets
+     * (because you want to use your own styles):
+     *
+     * ```php
+     * [
+     *     'yii\bootstrap\BootstrapAsset' => [
+     *         'css' => [],
+     *     ],
+     * ]
+     * ```
+     */
+    public $bundles = [];
+    /**
+     * @var string the root directory storing the published asset files.
+     */
+    public $basePath = '@webroot/assets';
+    /**
+     * @var string the base URL through which the published asset files can be accessed.
+     */
+    public $baseUrl = '@web/assets';
+    /**
+     * @var array mapping from source asset files (keys) to target asset files (values).
+     *
+     * This property is provided to support fixing incorrect asset file paths in some asset bundles.
+     * When an asset bundle is registered with a view, each relative asset file in its [[AssetBundle::css|css]]
+     * and [[AssetBundle::js|js]] arrays will be examined against this map. If any of the keys is found
+     * to be the last part of an asset file (which is prefixed with [[AssetBundle::sourcePath]] if available),
+     * the corresponding value will replace the asset and be registered with the view.
+     * For example, an asset file `my/path/to/jquery.js` matches a key `jquery.js`.
+     *
+     * Note that the target asset files should be absolute URLs, domain relative URLs (starting from '/') or paths
+     * relative to [[baseUrl]] and [[basePath]].
+     *
+     * In the following example, any assets ending with `jquery.min.js` will be replaced with `jquery/dist/jquery.js`
+     * which is relative to [[baseUrl]] and [[basePath]].
+     *
+     * ```php
+     * [
+     *     'jquery.min.js' => 'jquery/dist/jquery.js',
+     * ]
+     * ```
+     *
+     * You may also use aliases while specifying map value, for example:
+     *
+     * ```php
+     * [
+     *     'jquery.min.js' => '@web/js/jquery/jquery.js',
+     * ]
+     * ```
+     */
+    public $assetMap = [];
+    /**
+     * @var bool whether to use symbolic link to publish asset files. Defaults to false, meaning
+     * asset files are copied to [[basePath]]. Using symbolic links has the benefit that the published
+     * assets will always be consistent with the source assets and there is no copy operation required.
+     * This is especially useful during development.
+     *
+     * However, there are special requirements for hosting environments in order to use symbolic links.
+     * In particular, symbolic links are supported only on Linux/Unix, and Windows Vista/2008 or greater.
+     *
+     * Moreover, some Web servers need to be properly configured so that the linked assets are accessible
+     * to Web users. For example, for Apache Web server, the following configuration directive should be added
+     * for the Web folder:
+     *
+     * ```apache
+     * Options FollowSymLinks
+     * ```
+     */
+    public $linkAssets = false;
+    /**
+     * @var int the permission to be set for newly published asset files.
+     * This value will be used by PHP chmod() function. No umask will be applied.
+     * If not set, the permission will be determined by the current environment.
+     */
+    public $fileMode;
+    /**
+     * @var int the permission to be set for newly generated asset directories.
+     * This value will be used by PHP chmod() function. No umask will be applied.
+     * Defaults to 0775, meaning the directory is read-writable by owner and group,
+     * but read-only for other users.
+     */
+    public $dirMode = 0775;
+    /**
+     * @var callback a PHP callback that is called before copying each sub-directory or file.
+     * This option is used only when publishing a directory. If the callback returns false, the copy
+     * operation for the sub-directory or file will be cancelled.
+     *
+     * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
+     * file to be copied from, while `$to` is the copy target.
+     *
+     * This is passed as a parameter `beforeCopy` to [[\yii\helpers\FileHelper::copyDirectory()]].
+     */
+    public $beforeCopy;
+    /**
+     * @var callback a PHP callback that is called after a sub-directory or file is successfully copied.
+     * This option is used only when publishing a directory. The signature of the callback is the same as
+     * for [[beforeCopy]].
+     * This is passed as a parameter `afterCopy` to [[\yii\helpers\FileHelper::copyDirectory()]].
+     */
+    public $afterCopy;
+    /**
+     * @var bool whether the directory being published should be copied even if
+     * it is found in the target directory. This option is used only when publishing a directory.
+     * You may want to set this to be `true` during the development stage to make sure the published
+     * directory is always up-to-date. Do not set this to true on production servers as it will
+     * significantly degrade the performance.
+     */
+    public $forceCopy = false;
+    /**
+     * @var bool whether to append a timestamp to the URL of every published asset. When this is true,
+     * the URL of a published asset may look like `/path/to/asset?v=timestamp`, where `timestamp` is the
+     * last modification time of the published asset file.
+     * You normally would want to set this property to true when you have enabled HTTP caching for assets,
+     * because it allows you to bust caching when the assets are updated.
+     * @since 2.0.3
+     */
+    public $appendTimestamp = false;
+    /**
+     * @var callable a callback that will be called to produce hash for asset directory generation.
+     * The signature of the callback should be as follows:
+     *
+     * ```
+     * function ($path)
+     * ```
+     *
+     * where `$path` is the asset path. Note that the `$path` can be either directory where the asset
+     * files reside or a single file. For a CSS file that uses relative path in `url()`, the hash
+     * implementation should use the directory path of the file instead of the file path to include
+     * the relative asset files in the copying.
+     *
+     * If this is not set, the asset manager will use the default CRC32 and filemtime in the `hash`
+     * method.
+     *
+     * Example of an implementation using MD4 hash:
+     *
+     * ```php
+     * function ($path) {
+     *     return hash('md4', $path);
+     * }
+     * ```
+     *
+     * @since 2.0.6
+     */
+    public $hashCallback;
+
+    private $_dummyBundles = [];
+
+
+    /**
+     * Initializes the component.
+     * @throws InvalidConfigException if [[basePath]] does not exist.
+     */
+    public function init()
+    {
+        parent::init();
+        $this->basePath = Yii::getAlias($this->basePath);
+
+        $this->basePath = realpath($this->basePath);
+        $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
+    }
+
+    private $_isBasePathPermissionChecked;
+
+    /**
+     * Check whether the basePath exists and is writeable.
+     *
+     * @since 2.0.40
+     */
+    public function checkBasePathPermission()
+    {
+        // if the check is been done already, skip further checks
+        if ($this->_isBasePathPermissionChecked) {
+            return;
+        }
+
+        if (!is_dir($this->basePath)) {
+            throw new InvalidConfigException("The directory does not exist: {$this->basePath}");
+        }
+
+        if (!is_writable($this->basePath)) {
+            throw new InvalidConfigException("The directory is not writable by the Web process: {$this->basePath}");
+        }
+
+        $this->_isBasePathPermissionChecked = true;
+    }
+
+    /**
+     * Returns the named asset bundle.
+     *
+     * This method will first look for the bundle in [[bundles]]. If not found,
+     * it will treat `$name` as the class of the asset bundle and create a new instance of it.
+     *
+     * @param string $name the class name of the asset bundle (without the leading backslash)
+     * @param bool $publish whether to publish the asset files in the asset bundle before it is returned.
+     * If you set this false, you must manually call `AssetBundle::publish()` to publish the asset files.
+     * @return AssetBundle the asset bundle instance
+     * @throws InvalidConfigException if $name does not refer to a valid asset bundle
+     */
+    public function getBundle($name, $publish = true)
+    {
+        if ($this->bundles === false) {
+            return $this->loadDummyBundle($name);
+        } elseif (!isset($this->bundles[$name])) {
+            return $this->bundles[$name] = $this->loadBundle($name, [], $publish);
+        } elseif ($this->bundles[$name] instanceof AssetBundle) {
+            return $this->bundles[$name];
+        } elseif (is_array($this->bundles[$name])) {
+            return $this->bundles[$name] = $this->loadBundle($name, $this->bundles[$name], $publish);
+        } elseif ($this->bundles[$name] === false) {
+            return $this->loadDummyBundle($name);
+        }
+
+        throw new InvalidConfigException("Invalid asset bundle configuration: $name");
+    }
+
+    /**
+     * Loads asset bundle class by name.
+     *
+     * @param string $name bundle name
+     * @param array $config bundle object configuration
+     * @param bool $publish if bundle should be published
+     * @return AssetBundle
+     * @throws InvalidConfigException if configuration isn't valid
+     */
+    protected function loadBundle($name, $config = [], $publish = true)
+    {
+        if (!isset($config['class'])) {
+            $config['class'] = $name;
+        }
+        /* @var $bundle AssetBundle */
+        $bundle = Yii::createObject($config);
+        if ($publish) {
+            $bundle->publish($this);
+        }
+
+        return $bundle;
+    }
+
+    /**
+     * Loads dummy bundle by name.
+     *
+     * @param string $name
+     * @return AssetBundle
+     */
+    protected function loadDummyBundle($name)
+    {
+        if (!isset($this->_dummyBundles[$name])) {
+            $bundle = Yii::createObject(['class' => $name]);
+            $bundle->sourcePath = null;
+            $bundle->js = [];
+            $bundle->css = [];
+
+            $this->_dummyBundles[$name] = $bundle;
+        }
+
+        return $this->_dummyBundles[$name];
+    }
+
+    /**
+     * Returns the actual URL for the specified asset.
+     * The actual URL is obtained by prepending either [[AssetBundle::$baseUrl]] or [[AssetManager::$baseUrl]] to the given asset path.
+     * @param AssetBundle $bundle the asset bundle which the asset file belongs to
+     * @param string $asset the asset path. This should be one of the assets listed in [[AssetBundle::$js]] or [[AssetBundle::$css]].
+     * @param bool|null $appendTimestamp Whether to append timestamp to the URL.
+     * @return string the actual URL for the specified asset.
+     */
+    public function getAssetUrl($bundle, $asset, $appendTimestamp = null)
+    {
+        $assetUrl = $this->getActualAssetUrl($bundle, $asset);
+        $assetPath = $this->getAssetPath($bundle, $asset);
+
+        $withTimestamp = $this->appendTimestamp;
+        if ($appendTimestamp !== null) {
+            $withTimestamp = $appendTimestamp;
+        }
+
+        if ($withTimestamp && $assetPath && ($timestamp = @filemtime($assetPath)) > 0) {
+            return "$assetUrl?v=$timestamp";
+        }
+
+        return $assetUrl;
+    }
+
+    /**
+     * Returns the actual file path for the specified asset.
+     * @param AssetBundle $bundle the asset bundle which the asset file belongs to
+     * @param string $asset the asset path. This should be one of the assets listed in [[AssetBundle::$js]] or [[AssetBundle::$css]].
+     * @return string|false the actual file path, or `false` if the asset is specified as an absolute URL
+     */
+    public function getAssetPath($bundle, $asset)
+    {
+        if (($actualAsset = $this->resolveAsset($bundle, $asset)) !== false) {
+            return Url::isRelative($actualAsset) ? $this->basePath . '/' . $actualAsset : false;
+        }
+
+        return Url::isRelative($asset) ? $bundle->basePath . '/' . $asset : false;
+    }
+
+    /**
+     * @param AssetBundle $bundle
+     * @param string $asset
+     * @return string|bool
+     */
+    protected function resolveAsset($bundle, $asset)
+    {
+        if (isset($this->assetMap[$asset])) {
+            return $this->assetMap[$asset];
+        }
+        if ($bundle->sourcePath !== null && Url::isRelative($asset)) {
+            $asset = $bundle->sourcePath . '/' . $asset;
+        }
+
+        $n = mb_strlen($asset, Yii::$app->charset);
+        foreach ($this->assetMap as $from => $to) {
+            $n2 = mb_strlen($from, Yii::$app->charset);
+            if ($n2 <= $n && substr_compare($asset, $from, $n - $n2, $n2) === 0) {
+                return $to;
+            }
+        }
+
+        return false;
+    }
+
+    private $_converter;
+
+    /**
+     * Returns the asset converter.
+     * @return AssetConverterInterface the asset converter.
+     */
+    public function getConverter()
+    {
+        if ($this->_converter === null) {
+            $this->_converter = Yii::createObject(AssetConverter::className());
+        } elseif (is_array($this->_converter) || is_string($this->_converter)) {
+            if (is_array($this->_converter) && !isset($this->_converter['class'])) {
+                $this->_converter['class'] = AssetConverter::className();
+            }
+            $this->_converter = Yii::createObject($this->_converter);
+        }
+
+        return $this->_converter;
+    }
+
+    /**
+     * Sets the asset converter.
+     * @param array|AssetConverterInterface $value the asset converter. This can be either
+     * an object implementing the [[AssetConverterInterface]], or a configuration
+     * array that can be used to create the asset converter object.
+     */
+    public function setConverter($value)
+    {
+        $this->_converter = $value;
+    }
+
+    /**
+     * @var array published assets
+     */
+    private $_published = [];
+
+    /**
+     * Publishes a file or a directory.
+     *
+     * This method will copy the specified file or directory to [[basePath]] so that
+     * it can be accessed via the Web server.
+     *
+     * If the asset is a file, its file modification time will be checked to avoid
+     * unnecessary file copying.
+     *
+     * If the asset is a directory, all files and subdirectories under it will be published recursively.
+     * Note, in case $forceCopy is false the method only checks the existence of the target
+     * directory to avoid repetitive copying (which is very expensive).
+     *
+     * By default, when publishing a directory, subdirectories and files whose name starts with a dot "."
+     * will NOT be published. If you want to change this behavior, you may specify the "beforeCopy" option
+     * as explained in the `$options` parameter.
+     *
+     * Note: On rare scenario, a race condition can develop that will lead to a
+     * one-time-manifestation of a non-critical problem in the creation of the directory
+     * that holds the published assets. This problem can be avoided altogether by 'requesting'
+     * in advance all the resources that are supposed to trigger a 'publish()' call, and doing
+     * that in the application deployment phase, before system goes live. See more in the following
+     * discussion: http://code.google.com/p/yii/issues/detail?id=2579
+     *
+     * @param string $path the asset (file or directory) to be published
+     * @param array $options the options to be applied when publishing a directory.
+     * The following options are supported:
+     *
+     * - only: array, list of patterns that the file paths should match if they want to be copied.
+     * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied.
+     * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true.
+     * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
+     *   This overrides [[beforeCopy]] if set.
+     * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied.
+     *   This overrides [[afterCopy]] if set.
+     * - forceCopy: boolean, whether the directory being published should be copied even if
+     *   it is found in the target directory. This option is used only when publishing a directory.
+     *   This overrides [[forceCopy]] if set.
+     *
+     * @return array the path (directory or file path) and the URL that the asset is published as.
+     * @throws InvalidArgumentException if the asset to be published does not exist.
+     * @throws InvalidConfigException if the target directory [[basePath]] is not writeable.
+     */
+    public function publish($path, $options = [])
+    {
+        $path = Yii::getAlias($path);
+
+        if (isset($this->_published[$path])) {
+            return $this->_published[$path];
+        }
+
+        if (!is_string($path) || ($src = realpath($path)) === false) {
+            throw new InvalidArgumentException("The file or directory to be published does not exist: $path");
+        }
+
+        if (is_file($src)) {
+            return $this->_published[$path] = $this->publishFile($src);
+        }
+
+        return $this->_published[$path] = $this->publishDirectory($src, $options);
+    }
+
+    /**
+     * Publishes a file.
+     * @param string $src the asset file to be published
+     * @return string[] the path and the URL that the asset is published as.
+     * @throws InvalidArgumentException if the asset to be published does not exist.
+     */
+    protected function publishFile($src)
+    {
+        $this->checkBasePathPermission();
+
+        $dir = $this->hash($src);
+        $fileName = basename($src);
+        $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir;
+        $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName;
+
+        if (!is_dir($dstDir)) {
+            FileHelper::createDirectory($dstDir, $this->dirMode, true);
+        }
+
+        if ($this->linkAssets) {
+            if (!is_file($dstFile)) {
+                try { // fix #6226 symlinking multi threaded
+                    symlink($src, $dstFile);
+                } catch (\Exception $e) {
+                    if (!is_file($dstFile)) {
+                        throw $e;
+                    }
+                }
+            }
+        } elseif (@filemtime($dstFile) < @filemtime($src)) {
+            copy($src, $dstFile);
+            if ($this->fileMode !== null) {
+                @chmod($dstFile, $this->fileMode);
+            }
+        }
+
+        if ($this->appendTimestamp && ($timestamp = @filemtime($dstFile)) > 0) {
+            $fileName = $fileName . "?v=$timestamp";
+        }
+
+        return [$dstFile, $this->baseUrl . "/$dir/$fileName"];
+    }
+
+    /**
+     * Publishes a directory.
+     * @param string $src the asset directory to be published
+     * @param array $options the options to be applied when publishing a directory.
+     * The following options are supported:
+     *
+     * - only: array, list of patterns that the file paths should match if they want to be copied.
+     * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied.
+     * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true.
+     * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
+     *   This overrides [[beforeCopy]] if set.
+     * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied.
+     *   This overrides [[afterCopy]] if set.
+     * - forceCopy: boolean, whether the directory being published should be copied even if
+     *   it is found in the target directory. This option is used only when publishing a directory.
+     *   This overrides [[forceCopy]] if set.
+     *
+     * @return string[] the path directory and the URL that the asset is published as.
+     * @throws InvalidArgumentException if the asset to be published does not exist.
+     */
+    protected function publishDirectory($src, $options)
+    {
+        $this->checkBasePathPermission();
+
+        $dir = $this->hash($src);
+        $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir;
+        if ($this->linkAssets) {
+            if (!is_dir($dstDir)) {
+                FileHelper::createDirectory(dirname($dstDir), $this->dirMode, true);
+                try { // fix #6226 symlinking multi threaded
+                    symlink($src, $dstDir);
+                } catch (\Exception $e) {
+                    if (!is_dir($dstDir)) {
+                        throw $e;
+                    }
+                }
+            }
+        } elseif (!empty($options['forceCopy']) || ($this->forceCopy && !isset($options['forceCopy'])) || !is_dir($dstDir)) {
+            $opts = array_merge(
+                $options,
+                [
+                    'dirMode' => $this->dirMode,
+                    'fileMode' => $this->fileMode,
+                    'copyEmptyDirectories' => false,
+                ]
+            );
+            if (!isset($opts['beforeCopy'])) {
+                if ($this->beforeCopy !== null) {
+                    $opts['beforeCopy'] = $this->beforeCopy;
+                } else {
+                    $opts['beforeCopy'] = function ($from, $to) {
+                        return strncmp(basename($from), '.', 1) !== 0;
+                    };
+                }
+            }
+            if (!isset($opts['afterCopy']) && $this->afterCopy !== null) {
+                $opts['afterCopy'] = $this->afterCopy;
+            }
+            FileHelper::copyDirectory($src, $dstDir, $opts);
+        }
+
+        return [$dstDir, $this->baseUrl . '/' . $dir];
+    }
+
+    /**
+     * Returns the published path of a file path.
+     * This method does not perform any publishing. It merely tells you
+     * if the file or directory is published, where it will go.
+     * @param string $path directory or file path being published
+     * @return string|false string the published file path. False if the file or directory does not exist
+     */
+    public function getPublishedPath($path)
+    {
+        $path = Yii::getAlias($path);
+
+        if (isset($this->_published[$path])) {
+            return $this->_published[$path][0];
+        }
+        if (is_string($path) && ($path = realpath($path)) !== false) {
+            return $this->basePath . DIRECTORY_SEPARATOR . $this->hash($path) . (is_file($path) ? DIRECTORY_SEPARATOR . basename($path) : '');
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the URL of a published file path.
+     * This method does not perform any publishing. It merely tells you
+     * if the file path is published, what the URL will be to access it.
+     * @param string $path directory or file path being published
+     * @return string|false string the published URL for the file or directory. False if the file or directory does not exist.
+     */
+    public function getPublishedUrl($path)
+    {
+        $path = Yii::getAlias($path);
+
+        if (isset($this->_published[$path])) {
+            return $this->_published[$path][1];
+        }
+        if (is_string($path) && ($path = realpath($path)) !== false) {
+            return $this->baseUrl . '/' . $this->hash($path) . (is_file($path) ? '/' . basename($path) : '');
+        }
+
+        return false;
+    }
+
+    /**
+     * Generate a CRC32 hash for the directory path. Collisions are higher
+     * than MD5 but generates a much smaller hash string.
+     * @param string $path string to be hashed.
+     * @return string hashed string.
+     */
+    protected function hash($path)
+    {
+        if (is_callable($this->hashCallback)) {
+            return call_user_func($this->hashCallback, $path);
+        }
+        $path = (is_file($path) ? dirname($path) : $path) . filemtime($path);
+        return sprintf('%x', crc32($path . Yii::getVersion() . '|' . $this->linkAssets));
+    }
+
+    /**
+     * Returns the actual URL for the specified asset. Without parameters.
+     * The actual URL is obtained by prepending either [[AssetBundle::$baseUrl]] or [[AssetManager::$baseUrl]] to the given asset path.
+     * @param AssetBundle $bundle the asset bundle which the asset file belongs to
+     * @param string $asset the asset path. This should be one of the assets listed in [[AssetBundle::$js]] or [[AssetBundle::$css]].
+     * @return string the actual URL for the specified asset.
+     * @since 2.0.39
+     */
+    public function getActualAssetUrl($bundle, $asset)
+    {
+        if (($actualAsset = $this->resolveAsset($bundle, $asset)) !== false) {
+            if (strncmp($actualAsset, '@web/', 5) === 0) {
+                $asset = substr($actualAsset, 5);
+                $baseUrl = Yii::getAlias('@web');
+            } else {
+                $asset = Yii::getAlias($actualAsset);
+                $baseUrl = $this->baseUrl;
+            }
+        } else {
+            $baseUrl = $bundle->baseUrl;
+        }
+
+        if (!Url::isRelative($asset) || strncmp($asset, '/', 1) === 0) {
+            return $asset;
+        }
+
+        return "$baseUrl/$asset";
+    }
+}

+ 34 - 0
vendor/yiisoft/yii2/web/BadRequestHttpException.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * BadRequestHttpException represents a "Bad Request" HTTP exception with status code 400.
+ *
+ * Use this exception to represent a generic client error. In many cases, there
+ * may be an HTTP exception that more precisely describes the error. In that
+ * case, consider using the more precise exception to provide the user with
+ * additional information.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.1
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class BadRequestHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(400, $message, $code, $previous);
+    }
+}

+ 147 - 0
vendor/yiisoft/yii2/web/CacheSession.php

@@ -0,0 +1,147 @@
+<?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\caching\CacheInterface;
+use yii\di\Instance;
+
+/**
+ * CacheSession implements a session component using cache as storage medium.
+ *
+ * The cache being used can be any cache application component.
+ * The ID of the cache application component is specified via [[cache]], which defaults to 'cache'.
+ *
+ * Beware, by definition cache storage are volatile, which means the data stored on them
+ * may be swapped out and get lost. Therefore, you must make sure the cache used by this component
+ * is NOT volatile. If you want to use database as storage medium, [[DbSession]] is a better choice.
+ *
+ * The following example shows how you can configure the application to use CacheSession:
+ * Add the following to your application config under `components`:
+ *
+ * ```php
+ * 'session' => [
+ *     'class' => 'yii\web\CacheSession',
+ *     // 'cache' => 'mycache',
+ * ]
+ * ```
+ *
+ * @property-read bool $useCustomStorage Whether to use custom storage. This property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class CacheSession extends Session
+{
+    /**
+     * @var CacheInterface|array|string the cache object or the application component ID of the cache object.
+     * The session data will be stored using this cache object.
+     *
+     * After the CacheSession object is created, if you want to change this property,
+     * you should only assign it with a cache object.
+     *
+     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
+     */
+    public $cache = 'cache';
+
+
+    /**
+     * Initializes the application component.
+     */
+    public function init()
+    {
+        parent::init();
+        $this->cache = Instance::ensure($this->cache, 'yii\caching\CacheInterface');
+    }
+
+    /**
+     * Returns a value indicating whether to use custom session storage.
+     * This method overrides the parent implementation and always returns true.
+     * @return bool whether to use custom storage.
+     */
+    public function getUseCustomStorage()
+    {
+        return true;
+    }
+
+    /**
+     * Session open handler.
+     * @internal Do not call this method directly.
+     * @param string $savePath session save path
+     * @param string $sessionName session name
+     * @return bool whether session is opened successfully
+     */
+    public function openSession($savePath, $sessionName)
+    {
+        if ($this->getUseStrictMode()) {
+            $id = $this->getId();
+            if (!$this->cache->exists($this->calculateKey($id))) {
+                //This session id does not exist, mark it for forced regeneration
+                $this->_forceRegenerateId = $id;
+            }
+        }
+
+        return parent::openSession($savePath, $sessionName);
+    }
+
+    /**
+     * Session read handler.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @return string the session data
+     */
+    public function readSession($id)
+    {
+        $data = $this->cache->get($this->calculateKey($id));
+
+        return $data === false ? '' : $data;
+    }
+
+    /**
+     * Session write handler.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @param string $data session data
+     * @return bool whether session write is successful
+     */
+    public function writeSession($id, $data)
+    {
+        if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) {
+            //Ignore write when forceRegenerate is active for this id
+            return true;
+        }
+
+        return $this->cache->set($this->calculateKey($id), $data, $this->getTimeout());
+    }
+
+    /**
+     * Session destroy handler.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @return bool whether session is destroyed successfully
+     */
+    public function destroySession($id)
+    {
+        $cacheId = $this->calculateKey($id);
+        if ($this->cache->exists($cacheId) === false) {
+            return true;
+        }
+
+        return $this->cache->delete($cacheId);
+    }
+
+    /**
+     * Generates a unique key used for storing session data in cache.
+     * @param string $id session variable name
+     * @return mixed a safe cache key associated with the session variable name
+     */
+    protected function calculateKey($id)
+    {
+        return [__CLASS__, $id];
+    }
+}

+ 143 - 0
vendor/yiisoft/yii2/web/CompositeUrlRule.php

@@ -0,0 +1,143 @@
+<?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;
+
+/**
+ * CompositeUrlRule is the base class for URL rule classes that consist of multiple simpler rules.
+ *
+ * @property-read 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
+ */
+abstract class CompositeUrlRule extends BaseObject implements UrlRuleInterface
+{
+    /**
+     * @var UrlRuleInterface[] the URL rules contained in this composite rule.
+     * This property is set in [[init()]] by the return value of [[createRules()]].
+     */
+    protected $rules = [];
+    /**
+     * @var int|null status of the URL creation after the last [[createUrl()]] call.
+     * @since 2.0.12
+     */
+    protected $createStatus;
+
+
+    /**
+     * Creates the URL rules that should be contained within this composite rule.
+     * @return UrlRuleInterface[] the URL rules
+     */
+    abstract protected function createRules();
+
+    /**
+     * {@inheritdoc}
+     */
+    public function init()
+    {
+        parent::init();
+        $this->rules = $this->createRules();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function parseRequest($manager, $request)
+    {
+        foreach ($this->rules as $rule) {
+            /* @var $rule UrlRule */
+            $result = $rule->parseRequest($manager, $request);
+            if (YII_DEBUG) {
+                Yii::debug([
+                    'rule' => method_exists($rule, '__toString') ? $rule->__toString() : get_class($rule),
+                    'match' => $result !== false,
+                    'parent' => self::className(),
+                ], __METHOD__);
+            }
+            if ($result !== false) {
+                return $result;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createUrl($manager, $route, $params)
+    {
+        $this->createStatus = UrlRule::CREATE_STATUS_SUCCESS;
+        $url = $this->iterateRules($this->rules, $manager, $route, $params);
+        if ($url !== false) {
+            return $url;
+        }
+
+        if ($this->createStatus === UrlRule::CREATE_STATUS_SUCCESS) {
+            // create status was not changed - there is no rules configured
+            $this->createStatus = UrlRule::CREATE_STATUS_PARSING_ONLY;
+        }
+
+        return false;
+    }
+
+    /**
+     * Iterates through specified rules and calls [[createUrl()]] for each of them.
+     *
+     * @param UrlRuleInterface[] $rules rules to iterate.
+     * @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 bool|string the created URL, or `false` if none of specified rules cannot be used for creating this URL.
+     * @see createUrl()
+     * @since 2.0.12
+     */
+    protected function iterateRules($rules, $manager, $route, $params)
+    {
+        /* @var $rule UrlRule */
+        foreach ($rules as $rule) {
+            $url = $rule->createUrl($manager, $route, $params);
+            if ($url !== false) {
+                $this->createStatus = UrlRule::CREATE_STATUS_SUCCESS;
+                return $url;
+            }
+            if (
+                $this->createStatus === null
+                || !method_exists($rule, 'getCreateUrlStatus')
+                || $rule->getCreateUrlStatus() === null
+            ) {
+                $this->createStatus = null;
+            } else {
+                $this->createStatus |= $rule->getCreateUrlStatus();
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns status of the URL creation after the last [[createUrl()]] call.
+     *
+     * For multiple rules statuses will be combined by bitwise `or` operator
+     * (e.g. `UrlRule::CREATE_STATUS_PARSING_ONLY | UrlRule::CREATE_STATUS_PARAMS_MISMATCH`).
+     *
+     * @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
+     * @see https://secure.php.net/manual/en/language.operators.bitwise.php
+     * @since 2.0.12
+     */
+    public function getCreateUrlStatus()
+    {
+        return $this->createStatus;
+    }
+}

+ 29 - 0
vendor/yiisoft/yii2/web/ConflictHttpException.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * ConflictHttpException represents a "Conflict" HTTP exception with status code 409.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.8
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class ConflictHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(409, $message, $code, $previous);
+    }
+}

+ 318 - 0
vendor/yiisoft/yii2/web/Controller.php

@@ -0,0 +1,318 @@
+<?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\Exception;
+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).
+ *
+ * @property Request $request
+ * @property Response $response
+ *
+ * @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)
+    {
+        $this->response->format = Response::FORMAT_JSON;
+        $this->response->data = $data;
+        return $this->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)
+    {
+        $this->response->format = Response::FORMAT_XML;
+        $this->response->data = $data;
+        return $this->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 = [];
+        $requestedParams = [];
+        foreach ($method->getParameters() as $param) {
+            $name = $param->getName();
+            if (array_key_exists($name, $params)) {
+                $isValid = true;
+                if (PHP_VERSION_ID >= 80000) {
+                    $isArray = ($type = $param->getType()) instanceof \ReflectionNamedType && $type->getName() === 'array';
+                } else {
+                    $isArray = $param->isArray();
+                }
+                if ($isArray) {
+                    $params[$name] = (array)$params[$name];
+                } elseif (is_array($params[$name])) {
+                    $isValid = false;
+                } elseif (
+                    PHP_VERSION_ID >= 70000
+                    && ($type = $param->getType()) !== null
+                    && $type->isBuiltin()
+                    && ($params[$name] !== null || !$type->allowsNull())
+                ) {
+                    $typeName = PHP_VERSION_ID >= 70100 ? $type->getName() : (string)$type;
+
+                    if ($params[$name] === '' && $type->allowsNull()) {
+                        if ($typeName !== 'string') { // for old string behavior compatibility
+                            $params[$name] = null;
+                        }
+                    } else {
+                        switch ($typeName) {
+                            case 'int':
+                                $params[$name] = filter_var($params[$name], FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
+                                break;
+                            case 'float':
+                                $params[$name] = filter_var($params[$name], FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
+                                break;
+                            case 'bool':
+                                $params[$name] = filter_var($params[$name], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+                                break;
+                        }
+                        if ($params[$name] === null) {
+                            $isValid = false;
+                        }
+                    }
+                }
+                if (!$isValid) {
+                    throw new BadRequestHttpException(
+                        Yii::t('yii', 'Invalid data received for parameter "{param}".', ['param' => $name])
+                    );
+                }
+                $args[] = $actionParams[$name] = $params[$name];
+                unset($params[$name]);
+            } elseif (PHP_VERSION_ID >= 70100 && ($type = $param->getType()) !== null && !$type->isBuiltin()) {
+                try {
+                    $this->bindInjectedParams($type, $name, $args, $requestedParams);
+                } catch (HttpException $e) {
+                    throw $e;
+                } catch (Exception $e) {
+                    throw new ServerErrorHttpException($e->getMessage(), 0, $e);
+                }
+            } 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;
+
+        // We use a different array here, specifically one that doesn't contain service instances but descriptions instead.
+        if (Yii::$app->requestedParams === null) {
+            Yii::$app->requestedParams = array_merge($actionParams, $requestedParams);
+        }
+
+        return $args;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function beforeAction($action)
+    {
+        if (parent::beforeAction($action)) {
+            if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !$this->request->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 $this->response->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 $this->response->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 $this->response->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 $this->response->redirect($this->request->getUrl() . $anchor);
+    }
+}

+ 103 - 0
vendor/yiisoft/yii2/web/Cookie.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * Cookie represents information related with a cookie, such as [[name]], [[value]], [[domain]], etc.
+ *
+ * For more details and usage information on Cookie, see the [guide article on handling cookies](guide:runtime-sessions-cookies).
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Cookie extends \yii\base\BaseObject
+{
+    /**
+     * SameSite policy Lax will prevent the cookie from being sent by the browser in all cross-site browsing context
+     * during CSRF-prone request methods (e.g. POST, PUT, PATCH etc).
+     * E.g. a POST request from https://otherdomain.com to https://yourdomain.com will not include the cookie, however a GET request will.
+     * When a user follows a link from https://otherdomain.com to https://yourdomain.com it will include the cookie
+     * @see $sameSite
+     */
+    const SAME_SITE_LAX = 'Lax';
+    /**
+     * SameSite policy Strict will prevent the cookie from being sent by the browser in all cross-site browsing context
+     * regardless of the request method and even when following a regular link.
+     * E.g. a GET request from https://otherdomain.com to https://yourdomain.com or a user following a link from
+     * https://otherdomain.com to https://yourdomain.com will not include the cookie.
+     * @see $sameSite
+     */
+    const SAME_SITE_STRICT = 'Strict';
+    /**
+     * SameSite policy None disables the SameSite policy so cookies will be sent in all contexts,
+     * i.e in responses to both first-party and cross-origin requests.
+     * E.g. a POST request from https://otherdomain.com to https://yourdomain.com will include the cookie.
+     * Note: If `sameSite` is set to None, the `secure` attribute must be set to `true` (otherwise the cookie will be blocked by the browser).
+     * @see $sameSite
+     * @see $secure
+     * @since 2.0.43
+     */
+    const SAME_SITE_NONE = 'None';
+
+    /**
+     * @var string name of the cookie
+     */
+    public $name;
+    /**
+     * @var string value of the cookie
+     */
+    public $value = '';
+    /**
+     * @var string domain of the cookie
+     */
+    public $domain = '';
+    /**
+     * @var int the timestamp at which the cookie expires. This is the server timestamp.
+     * Defaults to 0, meaning "until the browser is closed".
+     */
+    public $expire = 0;
+    /**
+     * @var string the path on the server in which the cookie will be available on. The default is '/'.
+     */
+    public $path = '/';
+    /**
+     * @var bool whether cookie should be sent via secure connection
+     */
+    public $secure = false;
+    /**
+     * @var bool whether the cookie should be accessible only through the HTTP protocol.
+     * By setting this property to true, the cookie will not be accessible by scripting languages,
+     * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
+     */
+    public $httpOnly = true;
+    /**
+     * @var string SameSite prevents the browser from sending this cookie along with cross-site requests.
+     *
+     * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite for more information about sameSite.
+     *
+     * @since 2.0.21
+     */
+    public $sameSite = self::SAME_SITE_LAX;
+
+
+    /**
+     * Magic method to turn a cookie object into a string without having to explicitly access [[value]].
+     *
+     * ```php
+     * if (isset($request->cookies['name'])) {
+     *     $value = (string) $request->cookies['name'];
+     * }
+     * ```
+     *
+     * @return string The value of the cookie. If the value property is null, an empty string will be returned.
+     */
+    public function __toString()
+    {
+        return (string) $this->value;
+    }
+}

+ 244 - 0
vendor/yiisoft/yii2/web/CookieCollection.php

@@ -0,0 +1,244 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use ArrayIterator;
+use Yii;
+use yii\base\BaseObject;
+use yii\base\InvalidCallException;
+
+/**
+ * CookieCollection maintains the cookies available in the current request.
+ *
+ * For more details and usage information on CookieCollection, see the [guide article on handling cookies](guide:runtime-sessions-cookies).
+ *
+ * @property-read int $count The number of cookies in the collection. This property is read-only.
+ * @property-read ArrayIterator $iterator An iterator for traversing the cookies in the collection. This
+ * property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayAccess, \Countable
+{
+    /**
+     * @var bool whether this collection is read only.
+     */
+    public $readOnly = false;
+
+    /**
+     * @var Cookie[] the cookies in this collection (indexed by the cookie names)
+     */
+    private $_cookies;
+
+
+    /**
+     * Constructor.
+     * @param array $cookies the cookies that this collection initially contains. This should be
+     * an array of name-value pairs.
+     * @param array $config name-value pairs that will be used to initialize the object properties
+     */
+    public function __construct($cookies = [], $config = [])
+    {
+        $this->_cookies = $cookies;
+        parent::__construct($config);
+    }
+
+    /**
+     * Returns an iterator for traversing the cookies 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 cookies in the collection.
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->_cookies);
+    }
+
+    /**
+     * Returns the number of cookies 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 cookies in the collection.
+     */
+    public function count()
+    {
+        return $this->getCount();
+    }
+
+    /**
+     * Returns the number of cookies in the collection.
+     * @return int the number of cookies in the collection.
+     */
+    public function getCount()
+    {
+        return count($this->_cookies);
+    }
+
+    /**
+     * Returns the cookie with the specified name.
+     * @param string $name the cookie name
+     * @return Cookie|null the cookie with the specified name. Null if the named cookie does not exist.
+     * @see getValue()
+     */
+    public function get($name)
+    {
+        return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null;
+    }
+
+    /**
+     * Returns the value of the named cookie.
+     * @param string $name the cookie name
+     * @param mixed $defaultValue the value that should be returned when the named cookie does not exist.
+     * @return mixed the value of the named cookie.
+     * @see get()
+     */
+    public function getValue($name, $defaultValue = null)
+    {
+        return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue;
+    }
+
+    /**
+     * Returns whether there is a cookie with the specified name.
+     * Note that if a cookie is marked for deletion from browser, this method will return false.
+     * @param string $name the cookie name
+     * @return bool whether the named cookie exists
+     * @see remove()
+     */
+    public function has($name)
+    {
+        return isset($this->_cookies[$name]) && $this->_cookies[$name]->value !== ''
+            && ($this->_cookies[$name]->expire === null || $this->_cookies[$name]->expire === 0 || $this->_cookies[$name]->expire >= time());
+    }
+
+    /**
+     * Adds a cookie to the collection.
+     * If there is already a cookie with the same name in the collection, it will be removed first.
+     * @param Cookie $cookie the cookie to be added
+     * @throws InvalidCallException if the cookie collection is read only
+     */
+    public function add($cookie)
+    {
+        if ($this->readOnly) {
+            throw new InvalidCallException('The cookie collection is read only.');
+        }
+        $this->_cookies[$cookie->name] = $cookie;
+    }
+
+    /**
+     * Removes a cookie.
+     * If `$removeFromBrowser` is true, the cookie will be removed from the browser.
+     * In this case, a cookie with outdated expiry will be added to the collection.
+     * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
+     * @param bool $removeFromBrowser whether to remove the cookie from browser
+     * @throws InvalidCallException if the cookie collection is read only
+     */
+    public function remove($cookie, $removeFromBrowser = true)
+    {
+        if ($this->readOnly) {
+            throw new InvalidCallException('The cookie collection is read only.');
+        }
+        if ($cookie instanceof Cookie) {
+            $cookie->expire = 1;
+            $cookie->value = '';
+        } else {
+            $cookie = Yii::createObject([
+                'class' => 'yii\web\Cookie',
+                'name' => $cookie,
+                'expire' => 1,
+            ]);
+        }
+        if ($removeFromBrowser) {
+            $this->_cookies[$cookie->name] = $cookie;
+        } else {
+            unset($this->_cookies[$cookie->name]);
+        }
+    }
+
+    /**
+     * Removes all cookies.
+     * @throws InvalidCallException if the cookie collection is read only
+     */
+    public function removeAll()
+    {
+        if ($this->readOnly) {
+            throw new InvalidCallException('The cookie collection is read only.');
+        }
+        $this->_cookies = [];
+    }
+
+    /**
+     * Returns the collection as a PHP array.
+     * @return array the array representation of the collection.
+     * The array keys are cookie names, and the array values are the corresponding cookie objects.
+     */
+    public function toArray()
+    {
+        return $this->_cookies;
+    }
+
+    /**
+     * Populates the cookie collection from an array.
+     * @param array $array the cookies to populate from
+     * @since 2.0.3
+     */
+    public function fromArray(array $array)
+    {
+        $this->_cookies = $array;
+    }
+
+    /**
+     * Returns whether there is a cookie 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 cookie name
+     * @return bool whether the named cookie exists
+     */
+    public function offsetExists($name)
+    {
+        return $this->has($name);
+    }
+
+    /**
+     * Returns the cookie with the specified name.
+     * This method is required by the SPL interface [[\ArrayAccess]].
+     * It is implicitly called when you use something like `$cookie = $collection[$name];`.
+     * This is equivalent to [[get()]].
+     * @param string $name the cookie name
+     * @return Cookie the cookie with the specified name, null if the named cookie does not exist.
+     */
+    public function offsetGet($name)
+    {
+        return $this->get($name);
+    }
+
+    /**
+     * Adds the cookie to the collection.
+     * This method is required by the SPL interface [[\ArrayAccess]].
+     * It is implicitly called when you use something like `$collection[$name] = $cookie;`.
+     * This is equivalent to [[add()]].
+     * @param string $name the cookie name
+     * @param Cookie $cookie the cookie to be added
+     */
+    public function offsetSet($name, $cookie)
+    {
+        $this->add($cookie);
+    }
+
+    /**
+     * Removes the named cookie.
+     * 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 cookie name
+     */
+    public function offsetUnset($name)
+    {
+        $this->remove($name);
+    }
+}

+ 295 - 0
vendor/yiisoft/yii2/web/DbSession.php

@@ -0,0 +1,295 @@
+<?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\InvalidConfigException;
+use yii\db\Connection;
+use yii\db\PdoValue;
+use yii\db\Query;
+use yii\di\Instance;
+
+/**
+ * DbSession extends [[Session]] by using database as session data storage.
+ *
+ * By default, DbSession stores session data in a DB table named 'session'. This table
+ * must be pre-created. The table name can be changed by setting [[sessionTable]].
+ *
+ * The following example shows how you can configure the application to use DbSession:
+ * Add the following to your application config under `components`:
+ *
+ * ```php
+ * 'session' => [
+ *     'class' => 'yii\web\DbSession',
+ *     // 'db' => 'mydb',
+ *     // 'sessionTable' => 'my_session',
+ * ]
+ * ```
+ *
+ * DbSession extends [[MultiFieldSession]], thus it allows saving extra fields into the [[sessionTable]].
+ * Refer to [[MultiFieldSession]] for more details.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class DbSession extends MultiFieldSession
+{
+    /**
+     * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
+     * After the DbSession object is created, if you want to change this property, you should only assign it
+     * with a DB connection object.
+     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
+     */
+    public $db = 'db';
+    /**
+     * @var string the name of the DB table that stores the session data.
+     * The table should be pre-created as follows:
+     *
+     * ```sql
+     * CREATE TABLE session
+     * (
+     *     id CHAR(40) NOT NULL PRIMARY KEY,
+     *     expire INTEGER,
+     *     data BLOB
+     * )
+     * ```
+     *
+     * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
+     * that can be used for some popular DBMS:
+     *
+     * - MySQL: LONGBLOB
+     * - PostgreSQL: BYTEA
+     * - MSSQL: BLOB
+     *
+     * When using DbSession in a production server, we recommend you create a DB index for the 'expire'
+     * column in the session table to improve the performance.
+     *
+     * Note that according to the php.ini setting of `session.hash_function`, you may need to adjust
+     * the length of the `id` column. For example, if `session.hash_function=sha256`, you should use
+     * length 64 instead of 40.
+     */
+    public $sessionTable = '{{%session}}';
+
+    /**
+     * @var array Session fields to be written into session table columns
+     * @since 2.0.17
+     */
+    protected $fields = [];
+
+
+    /**
+     * Initializes the DbSession component.
+     * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+     * @throws InvalidConfigException if [[db]] is invalid.
+     */
+    public function init()
+    {
+        parent::init();
+        $this->db = Instance::ensure($this->db, Connection::className());
+    }
+
+    /**
+     * Session open handler.
+     * @internal Do not call this method directly.
+     * @param string $savePath session save path
+     * @param string $sessionName session name
+     * @return bool whether session is opened successfully
+     */
+    public function openSession($savePath, $sessionName)
+    {
+        if ($this->getUseStrictMode()) {
+            $id = $this->getId();
+            if (!$this->getReadQuery($id)->exists($this->db)) {
+                //This session id does not exist, mark it for forced regeneration
+                $this->_forceRegenerateId = $id;
+            }
+        }
+
+        return parent::openSession($savePath, $sessionName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function regenerateID($deleteOldSession = false)
+    {
+        $oldID = session_id();
+
+        // if no session is started, there is nothing to regenerate
+        if (empty($oldID)) {
+            return;
+        }
+
+        parent::regenerateID(false);
+        $newID = session_id();
+        // if session id regeneration failed, no need to create/update it.
+        if (empty($newID)) {
+            Yii::warning('Failed to generate new session ID', __METHOD__);
+            return;
+        }
+
+        $row = $this->db->useMaster(function() use ($oldID) {
+            return (new Query())->from($this->sessionTable)
+               ->where(['id' => $oldID])
+               ->createCommand($this->db)
+               ->queryOne();
+        });
+
+        if ($row !== false) {
+            if ($deleteOldSession) {
+                $this->db->createCommand()
+                    ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID])
+                    ->execute();
+            } else {
+                $row['id'] = $newID;
+                $this->db->createCommand()
+                    ->insert($this->sessionTable, $row)
+                    ->execute();
+            }
+        } else {
+            // shouldn't reach here normally
+            $this->db->createCommand()
+                ->insert($this->sessionTable, $this->composeFields($newID, ''))
+                ->execute();
+        }
+    }
+
+    /**
+     * Ends the current session and store session data.
+     * @since 2.0.17
+     */
+    public function close()
+    {
+        if ($this->getIsActive()) {
+            // prepare writeCallback fields before session closes
+            $this->fields = $this->composeFields();
+            YII_DEBUG ? session_write_close() : @session_write_close();
+        }
+    }
+
+    /**
+     * Session read handler.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @return string the session data
+     */
+    public function readSession($id)
+    {
+        $query = $this->getReadQuery($id);
+
+        if ($this->readCallback !== null) {
+            $fields = $query->one($this->db);
+            return $fields === false ? '' : $this->extractData($fields);
+        }
+
+        $data = $query->select(['data'])->scalar($this->db);
+        return $data === false ? '' : $data;
+    }
+
+    /**
+     * Session write handler.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @param string $data session data
+     * @return bool whether session write is successful
+     */
+    public function writeSession($id, $data)
+    {
+        if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) {
+            //Ignore write when forceRegenerate is active for this id
+            return true;
+        }
+
+        // exception must be caught in session write handler
+        // https://secure.php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes
+        try {
+            // ensure backwards compatability (fixed #9438)
+            if ($this->writeCallback && !$this->fields) {
+                $this->fields = $this->composeFields();
+            }
+            // ensure data consistency
+            if (!isset($this->fields['data'])) {
+                $this->fields['data'] = $data;
+            } else {
+                $_SESSION = $this->fields['data'];
+            }
+            // ensure 'id' and 'expire' are never affected by [[writeCallback]]
+            $this->fields = array_merge($this->fields, [
+                'id' => $id,
+                'expire' => time() + $this->getTimeout(),
+            ]);
+            $this->fields = $this->typecastFields($this->fields);
+            $this->db->createCommand()->upsert($this->sessionTable, $this->fields)->execute();
+            $this->fields = [];
+        } catch (\Exception $e) {
+            Yii::$app->errorHandler->handleException($e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Session destroy handler.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @return bool whether session is destroyed successfully
+     */
+    public function destroySession($id)
+    {
+        $this->db->createCommand()
+            ->delete($this->sessionTable, ['id' => $id])
+            ->execute();
+
+        return true;
+    }
+
+    /**
+     * Session GC (garbage collection) handler.
+     * @internal Do not call this method directly.
+     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
+     * @return bool whether session is GCed successfully
+     */
+    public function gcSession($maxLifetime)
+    {
+        $this->db->createCommand()
+            ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()])
+            ->execute();
+
+        return true;
+    }
+
+    /**
+     * Generates a query to get the session from db
+     * @param string $id The id of the session
+     * @return Query
+     */
+    protected function getReadQuery($id)
+    {
+        return (new Query())
+            ->from($this->sessionTable)
+            ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]);
+    }
+
+    /**
+     * Method typecasts $fields before passing them to PDO.
+     * Default implementation casts field `data` to `\PDO::PARAM_LOB`.
+     * You can override this method in case you need special type casting.
+     *
+     * @param array $fields Fields, that will be passed to PDO. Key - name, Value - value
+     * @return array
+     * @since 2.0.13
+     */
+    protected function typecastFields($fields)
+    {
+        if (isset($fields['data']) && !is_array($fields['data']) && !is_object($fields['data'])) {
+            $fields['data'] = new PdoValue($fields['data'], \PDO::PARAM_LOB);
+        }
+
+        return $fields;
+    }
+}

+ 221 - 0
vendor/yiisoft/yii2/web/ErrorAction.php

@@ -0,0 +1,221 @@
+<?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\Action;
+use yii\base\Exception;
+use yii\base\UserException;
+
+/**
+ * ErrorAction displays application errors using a specified view.
+ *
+ * To use ErrorAction, you need to do the following steps:
+ *
+ * First, declare an action of ErrorAction type in the `actions()` method of your `SiteController`
+ * class (or whatever controller you prefer), like the following:
+ *
+ * ```php
+ * public function actions()
+ * {
+ *     return [
+ *         'error' => ['class' => 'yii\web\ErrorAction'],
+ *     ];
+ * }
+ * ```
+ *
+ * Then, create a view file for this action. If the route of your error action is `site/error`, then
+ * the view file should be `views/site/error.php`. In this view file, the following variables are available:
+ *
+ * - `$name`: the error name
+ * - `$message`: the error message
+ * - `$exception`: the exception being handled
+ *
+ * Finally, configure the "errorHandler" application component as follows,
+ *
+ * ```php
+ * 'errorHandler' => [
+ *     'errorAction' => 'site/error',
+ * ]
+ * ```
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Dmitry Naumenko <d.naumenko.a@gmail.com>
+ * @since 2.0
+ */
+class ErrorAction extends Action
+{
+    /**
+     * @var string the view file to be rendered. If not set, it will take the value of [[id]].
+     * That means, if you name the action as "error" in "SiteController", then the view name
+     * would be "error", and the corresponding view file would be "views/site/error.php".
+     */
+    public $view;
+    /**
+     * @var string the name of the error when the exception name cannot be determined.
+     * Defaults to "Error".
+     */
+    public $defaultName;
+    /**
+     * @var string the message to be displayed when the exception message contains sensitive information.
+     * Defaults to "An internal server error occurred.".
+     */
+    public $defaultMessage;
+    /**
+     * @var string|false|null the name of the layout to be applied to this error action view.
+     * If not set, the layout configured in the controller will be used.
+     * @see \yii\base\Controller::$layout
+     * @since 2.0.14
+     */
+    public $layout;
+
+    /**
+     * @var \Exception the exception object, normally is filled on [[init()]] method call.
+     * @see [[findException()]] to know default way of obtaining exception.
+     * @since 2.0.11
+     */
+    protected $exception;
+
+
+    /**
+     * {@inheritdoc}
+     */
+    public function init()
+    {
+        $this->exception = $this->findException();
+
+        if ($this->defaultMessage === null) {
+            $this->defaultMessage = Yii::t('yii', 'An internal server error occurred.');
+        }
+
+        if ($this->defaultName === null) {
+            $this->defaultName = Yii::t('yii', 'Error');
+        }
+    }
+
+    /**
+     * Runs the action.
+     *
+     * @return string result content
+     */
+    public function run()
+    {
+        if ($this->layout !== null) {
+            $this->controller->layout = $this->layout;
+        }
+
+        Yii::$app->getResponse()->setStatusCodeByException($this->exception);
+
+        if (Yii::$app->getRequest()->getIsAjax()) {
+            return $this->renderAjaxResponse();
+        }
+
+        return $this->renderHtmlResponse();
+    }
+
+    /**
+     * Builds string that represents the exception.
+     * Normally used to generate a response to AJAX request.
+     * @return string
+     * @since 2.0.11
+     */
+    protected function renderAjaxResponse()
+    {
+        return $this->getExceptionName() . ': ' . $this->getExceptionMessage();
+    }
+
+    /**
+     * Renders a view that represents the exception.
+     * @return string
+     * @since 2.0.11
+     */
+    protected function renderHtmlResponse()
+    {
+        return $this->controller->render($this->view ?: $this->id, $this->getViewRenderParams());
+    }
+
+    /**
+     * Builds array of parameters that will be passed to the view.
+     * @return array
+     * @since 2.0.11
+     */
+    protected function getViewRenderParams()
+    {
+        return [
+            'name' => $this->getExceptionName(),
+            'message' => $this->getExceptionMessage(),
+            'exception' => $this->exception,
+        ];
+    }
+
+    /**
+     * Gets exception from the [[yii\web\ErrorHandler|ErrorHandler]] component.
+     * In case there is no exception in the component, treat as the action has been invoked
+     * not from error handler, but by direct route, so '404 Not Found' error will be displayed.
+     * @return \Exception
+     * @since 2.0.11
+     */
+    protected function findException()
+    {
+        if (($exception = Yii::$app->getErrorHandler()->exception) === null) {
+            $exception = new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
+        }
+
+        return $exception;
+    }
+
+    /**
+     * Gets the code from the [[exception]].
+     * @return mixed
+     * @since 2.0.11
+     */
+    protected function getExceptionCode()
+    {
+        if ($this->exception instanceof HttpException) {
+            return $this->exception->statusCode;
+        }
+
+        return $this->exception->getCode();
+    }
+
+    /**
+     * Returns the exception name, followed by the code (if present).
+     *
+     * @return string
+     * @since 2.0.11
+     */
+    protected function getExceptionName()
+    {
+        if ($this->exception instanceof Exception) {
+            $name = $this->exception->getName();
+        } else {
+            $name = $this->defaultName;
+        }
+
+        if ($code = $this->getExceptionCode()) {
+            $name .= " (#$code)";
+        }
+
+        return $name;
+    }
+
+    /**
+     * Returns the [[exception]] message for [[yii\base\UserException]] only.
+     * For other cases [[defaultMessage]] will be returned.
+     * @return string
+     * @since 2.0.11
+     */
+    protected function getExceptionMessage()
+    {
+        if ($this->exception instanceof UserException) {
+            return $this->exception->getMessage();
+        }
+
+        return $this->defaultMessage;
+    }
+}

+ 529 - 0
vendor/yiisoft/yii2/web/ErrorHandler.php

@@ -0,0 +1,529 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use common\helpers\DingTalk;
+use common\helpers\LoggerTool;
+use Yii;
+use yii\base\ErrorException;
+use yii\base\Exception;
+use yii\base\UserException;
+use yii\helpers\VarDumper;
+
+/**
+ * ErrorHandler handles uncaught PHP errors and exceptions.
+ *
+ * ErrorHandler displays these errors using appropriate views based on the
+ * nature of the errors and the mode the application runs at.
+ *
+ * ErrorHandler is configured as an application component in [[\yii\base\Application]] by default.
+ * You can access that instance via `Yii::$app->errorHandler`.
+ *
+ * For more details and usage information on ErrorHandler, see the [guide article on handling errors](guide:runtime-handling-errors).
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Timur Ruziev <resurtm@gmail.com>
+ * @since 2.0
+ */
+class ErrorHandler extends \yii\base\ErrorHandler
+{
+    /**
+     * @var int maximum number of source code lines to be displayed. Defaults to 19.
+     */
+    public $maxSourceLines = 19;
+    /**
+     * @var int maximum number of trace source code lines to be displayed. Defaults to 13.
+     */
+    public $maxTraceSourceLines = 13;
+    /**
+     * @var string the route (e.g. `site/error`) to the controller action that will be used
+     * to display external errors. Inside the action, it can retrieve the error information
+     * using `Yii::$app->errorHandler->exception`. This property defaults to null, meaning ErrorHandler
+     * will handle the error display.
+     */
+    public $errorAction;
+    /**
+     * @var string the path of the view file for rendering exceptions without call stack information.
+     */
+    public $errorView = '@yii/views/errorHandler/error.php';
+    /**
+     * @var string the path of the view file for rendering exceptions.
+     */
+    public $exceptionView = '@yii/views/errorHandler/exception.php';
+    /**
+     * @var string the path of the view file for rendering exceptions and errors call stack element.
+     */
+    public $callStackItemView = '@yii/views/errorHandler/callStackItem.php';
+    /**
+     * @var string the path of the view file for rendering previous exceptions.
+     */
+    public $previousExceptionView = '@yii/views/errorHandler/previousException.php';
+    /**
+     * @var array list of the PHP predefined variables that should be displayed on the error page.
+     * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be displayed.
+     * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION']`.
+     * @see renderRequest()
+     * @since 2.0.7
+     */
+    public $displayVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION'];
+    /**
+     * @var string trace line with placeholders to be be substituted.
+     * The placeholders are {file}, {line} and {text} and the string should be as follows.
+     *
+     * `File: {file} - Line: {line} - Text: {text}`
+     *
+     * @example <a href="ide://open?file={file}&line={line}">{html}</a>
+     * @see https://github.com/yiisoft/yii2-debug#open-files-in-ide
+     * @since 2.0.14
+     */
+    public $traceLine = '{html}';
+
+
+    /**
+     * Renders the exception.
+     * @param \Exception|\Error $exception the exception to be rendered.
+     */
+    protected function renderException($exception)
+    {
+        if (Yii::$app->has('response')) {
+            $response = Yii::$app->getResponse();
+            // reset parameters of response to avoid interference with partially created response data
+            // in case the error occurred while sending the response.
+            $response->isSent = false;
+            $response->stream = null;
+            $response->data = null;
+            $response->content = null;
+        } else {
+            $response = new Response();
+        }
+
+        $response->setStatusCodeByException($exception);
+
+        $useErrorView = $response->format === Response::FORMAT_HTML && (!YII_DEBUG || $exception instanceof UserException);
+
+        if ($useErrorView && $this->errorAction !== null) {
+            Yii::$app->view->clear();
+            $result = Yii::$app->runAction($this->errorAction);
+            if ($result instanceof Response) {
+                $response = $result;
+            } else {
+                $response->data = $result;
+            }
+        } elseif ($response->format === Response::FORMAT_HTML) {
+            if ($this->shouldRenderSimpleHtml()) {
+                // AJAX request
+                $response->data = '<pre>' . $this->htmlEncode(static::convertExceptionToString($exception)) . '</pre>';
+            } else {
+                // if there is an error during error rendering it's useful to
+                // display PHP error in debug mode instead of a blank screen
+                if (YII_DEBUG) {
+                    ini_set('display_errors', 1);
+                }
+                $file = $useErrorView ? $this->errorView : $this->exceptionView;
+                $response->data = $this->renderFile($file, [
+                    'exception' => $exception,
+                ]);
+            }
+        } elseif ($response->format === Response::FORMAT_RAW) {
+            $response->data = static::convertExceptionToString($exception);
+        } else {
+            $response->data = $this->convertExceptionToArray($exception);
+        }
+
+        $response->send();
+    }
+
+    /**
+     * Converts an exception into an array.
+     * @param \Exception|\Error $exception the exception being converted
+     * @return array the array representation of the exception.
+     */
+    protected function convertExceptionToArray($exception)
+    {
+        if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) {
+            $exception = new HttpException(500, Yii::t('yii', 'An internal server error occurred.'));
+        }
+
+        $errorOutline = ['AR_', 'PHP', 'Undefined', 'Undefined index'];
+        foreach ($errorOutline as $item) {
+            if (false !== strpos($exception->getMessage(), $item)) {
+                $it = [
+                    'name' => ($exception instanceof Exception || $exception instanceof ErrorException) ? $exception->getName() : 'Exception',
+                    'message' => '提醒:' . $exception->getMessage(),
+                    'code' => $exception->getCode(),
+                ];
+                if ($exception instanceof HttpException) {
+                    $it['status'] = $exception->statusCode;
+                }
+                if (YII_DEBUG) {
+                    $it['type'] = get_class($exception);
+                    if (!$exception instanceof UserException) {
+                        $it['file'] = $exception->getFile();
+                        $it['line'] = $exception->getLine();
+                        $it['stack-trace'] = explode("\n", $exception->getTraceAsString());
+                        if ($exception instanceof \yii\db\Exception) {
+                            $it['error-info'] = $exception->errorInfo;
+                        }
+                    }
+                }
+                if (($prev = $exception->getPrevious()) !== null) {
+                    $it['previous'] = $this->convertExceptionToArray($prev);
+                }
+
+                // 发送钉钉提醒
+                DingTalk::sendNotice($it);
+
+                $array = [
+                    'name' => ($exception instanceof Exception || $exception instanceof ErrorException) ? $exception->getName() : 'Exception',
+                    'message' => '系统异常',
+                    'code' => $exception->getCode(),
+                ];
+
+                return $array;
+            }
+        }
+
+        $array = [
+            'name' => ($exception instanceof Exception || $exception instanceof ErrorException) ? $exception->getName() : 'Exception',
+            'message' => $exception->getMessage(),
+            'code' => $exception->getCode(),
+        ];
+
+        return $array;
+    }
+
+    /**
+     * Converts special characters to HTML entities.
+     * @param string $text to encode.
+     * @return string encoded original text.
+     */
+    public function htmlEncode($text)
+    {
+        return htmlspecialchars($text, ENT_NOQUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
+    }
+
+    /**
+     * Adds informational links to the given PHP type/class.
+     * @param string $code type/class name to be linkified.
+     * @return string linkified with HTML type/class name.
+     */
+    public function addTypeLinks($code)
+    {
+        if (preg_match('/(.*?)::([^(]+)/', $code, $matches)) {
+            $class = $matches[1];
+            $method = $matches[2];
+            $text = $this->htmlEncode($class) . '::' . $this->htmlEncode($method);
+        } else {
+            $class = $code;
+            $method = null;
+            $text = $this->htmlEncode($class);
+        }
+
+        $url = null;
+
+        $shouldGenerateLink = true;
+        if ($method !== null && substr_compare($method, '{closure}', -9) !== 0) {
+            $reflection = new \ReflectionClass($class);
+            if ($reflection->hasMethod($method)) {
+                $reflectionMethod = $reflection->getMethod($method);
+                $shouldGenerateLink = $reflectionMethod->isPublic() || $reflectionMethod->isProtected();
+            } else {
+                $shouldGenerateLink = false;
+            }
+        }
+
+        if ($shouldGenerateLink) {
+            $url = $this->getTypeUrl($class, $method);
+        }
+
+        if ($url === null) {
+            return $text;
+        }
+
+        return '<a href="' . $url . '" target="_blank">' . $text . '</a>';
+    }
+
+    /**
+     * Returns the informational link URL for a given PHP type/class.
+     * @param string $class the type or class name.
+     * @param string|null $method the method name.
+     * @return string|null the informational link URL.
+     * @see addTypeLinks()
+     */
+    protected function getTypeUrl($class, $method)
+    {
+        if (strncmp($class, 'yii\\', 4) !== 0) {
+            return null;
+        }
+
+        $page = $this->htmlEncode(strtolower(str_replace('\\', '-', $class)));
+        $url = "http://www.yiiframework.com/doc-2.0/$page.html";
+        if ($method) {
+            $url .= "#$method()-detail";
+        }
+
+        return $url;
+    }
+
+    /**
+     * Renders a view file as a PHP script.
+     * @param string $_file_ the view file.
+     * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
+     * @return string the rendering result
+     */
+    public function renderFile($_file_, $_params_)
+    {
+        $_params_['handler'] = $this;
+        if ($this->exception instanceof ErrorException || !Yii::$app->has('view')) {
+            ob_start();
+            ob_implicit_flush(false);
+            extract($_params_, EXTR_OVERWRITE);
+            require Yii::getAlias($_file_);
+
+            return ob_get_clean();
+        }
+
+        $view = Yii::$app->getView();
+        $view->clear();
+
+        return $view->renderFile($_file_, $_params_, $this);
+    }
+
+    /**
+     * Renders the previous exception stack for a given Exception.
+     * @param \Exception $exception the exception whose precursors should be rendered.
+     * @return string HTML content of the rendered previous exceptions.
+     * Empty string if there are none.
+     */
+    public function renderPreviousExceptions($exception)
+    {
+        if (($previous = $exception->getPrevious()) !== null) {
+            return $this->renderFile($this->previousExceptionView, ['exception' => $previous]);
+        }
+
+        return '';
+    }
+
+    /**
+     * Renders a single call stack element.
+     * @param string|null $file name where call has happened.
+     * @param int|null $line number on which call has happened.
+     * @param string|null $class called class name.
+     * @param string|null $method called function/method name.
+     * @param array $args array of method arguments.
+     * @param int $index number of the call stack element.
+     * @return string HTML content of the rendered call stack element.
+     */
+    public function renderCallStackItem($file, $line, $class, $method, $args, $index)
+    {
+        $lines = [];
+        $begin = $end = 0;
+        if ($file !== null && $line !== null) {
+            $line--; // adjust line number from one-based to zero-based
+            $lines = @file($file);
+            if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line) {
+                return '';
+            }
+
+            $half = (int) (($index === 1 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2);
+            $begin = $line - $half > 0 ? $line - $half : 0;
+            $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1;
+        }
+
+        return $this->renderFile($this->callStackItemView, [
+            'file' => $file,
+            'line' => $line,
+            'class' => $class,
+            'method' => $method,
+            'index' => $index,
+            'lines' => $lines,
+            'begin' => $begin,
+            'end' => $end,
+            'args' => $args,
+        ]);
+    }
+
+    /**
+     * Renders call stack.
+     * @param \Exception|\ParseError $exception exception to get call stack from
+     * @return string HTML content of the rendered call stack.
+     * @since 2.0.12
+     */
+    public function renderCallStack($exception)
+    {
+        $out = '<ul>';
+        $out .= $this->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, [], 1);
+        for ($i = 0, $trace = $exception->getTrace(), $length = count($trace); $i < $length; ++$i) {
+            $file = !empty($trace[$i]['file']) ? $trace[$i]['file'] : null;
+            $line = !empty($trace[$i]['line']) ? $trace[$i]['line'] : null;
+            $class = !empty($trace[$i]['class']) ? $trace[$i]['class'] : null;
+            $function = null;
+            if (!empty($trace[$i]['function']) && $trace[$i]['function'] !== 'unknown') {
+                $function = $trace[$i]['function'];
+            }
+            $args = !empty($trace[$i]['args']) ? $trace[$i]['args'] : [];
+            $out .= $this->renderCallStackItem($file, $line, $class, $function, $args, $i + 2);
+        }
+        $out .= '</ul>';
+        return $out;
+    }
+
+    /**
+     * Renders the global variables of the request.
+     * List of global variables is defined in [[displayVars]].
+     * @return string the rendering result
+     * @see displayVars
+     */
+    public function renderRequest()
+    {
+        $request = '';
+        foreach ($this->displayVars as $name) {
+            if (!empty($GLOBALS[$name])) {
+                $request .= '$' . $name . ' = ' . VarDumper::export($GLOBALS[$name]) . ";\n\n";
+            }
+        }
+
+        return '<pre>' . $this->htmlEncode(rtrim($request, "\n")) . '</pre>';
+    }
+
+    /**
+     * Determines whether given name of the file belongs to the framework.
+     * @param string $file name to be checked.
+     * @return bool whether given name of the file belongs to the framework.
+     */
+    public function isCoreFile($file)
+    {
+        return $file === null || strpos(realpath($file), YII2_PATH . DIRECTORY_SEPARATOR) === 0;
+    }
+
+    /**
+     * Creates HTML containing link to the page with the information on given HTTP status code.
+     * @param int $statusCode to be used to generate information link.
+     * @param string $statusDescription Description to display after the the status code.
+     * @return string generated HTML with HTTP status code information.
+     */
+    public function createHttpStatusLink($statusCode, $statusDescription)
+    {
+        return '<a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#' . (int) $statusCode . '" target="_blank">HTTP ' . (int) $statusCode . ' &ndash; ' . $statusDescription . '</a>';
+    }
+
+    /**
+     * Creates string containing HTML link which refers to the home page of determined web-server software
+     * and its full name.
+     * @return string server software information hyperlink.
+     */
+    public function createServerInformationLink()
+    {
+        $serverUrls = [
+            'http://httpd.apache.org/' => ['apache'],
+            'http://nginx.org/' => ['nginx'],
+            'http://lighttpd.net/' => ['lighttpd'],
+            'http://gwan.com/' => ['g-wan', 'gwan'],
+            'http://iis.net/' => ['iis', 'services'],
+            'https://secure.php.net/manual/en/features.commandline.webserver.php' => ['development'],
+        ];
+        if (isset($_SERVER['SERVER_SOFTWARE'])) {
+            foreach ($serverUrls as $url => $keywords) {
+                foreach ($keywords as $keyword) {
+                    if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
+                        return '<a href="' . $url . '" target="_blank">' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '</a>';
+                    }
+                }
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * Creates string containing HTML link which refers to the page with the current version
+     * of the framework and version number text.
+     * @return string framework version information hyperlink.
+     */
+    public function createFrameworkVersionLink()
+    {
+        return '<a href="http://github.com/yiisoft/yii2/" target="_blank">' . $this->htmlEncode(Yii::getVersion()) . '</a>';
+    }
+
+    /**
+     * Converts arguments array to its string representation.
+     *
+     * @param array $args arguments array to be converted
+     * @return string string representation of the arguments array
+     */
+    public function argumentsToString($args)
+    {
+        $count = 0;
+        $isAssoc = $args !== array_values($args);
+
+        foreach ($args as $key => $value) {
+            $count++;
+            if ($count >= 5) {
+                if ($count > 5) {
+                    unset($args[$key]);
+                } else {
+                    $args[$key] = '...';
+                }
+                continue;
+            }
+
+            if (is_object($value)) {
+                $args[$key] = '<span class="title">' . $this->htmlEncode(get_class($value)) . '</span>';
+            } elseif (is_bool($value)) {
+                $args[$key] = '<span class="keyword">' . ($value ? 'true' : 'false') . '</span>';
+            } elseif (is_string($value)) {
+                $fullValue = $this->htmlEncode($value);
+                if (mb_strlen($value, 'UTF-8') > 32) {
+                    $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'UTF-8')) . '...';
+                    $args[$key] = "<span class=\"string\" title=\"$fullValue\">'$displayValue'</span>";
+                } else {
+                    $args[$key] = "<span class=\"string\">'$fullValue'</span>";
+                }
+            } elseif (is_array($value)) {
+                $args[$key] = '[' . $this->argumentsToString($value) . ']';
+            } elseif ($value === null) {
+                $args[$key] = '<span class="keyword">null</span>';
+            } elseif (is_resource($value)) {
+                $args[$key] = '<span class="keyword">resource</span>';
+            } else {
+                $args[$key] = '<span class="number">' . $value . '</span>';
+            }
+
+            if (is_string($key)) {
+                $args[$key] = '<span class="string">\'' . $this->htmlEncode($key) . "'</span> => $args[$key]";
+            } elseif ($isAssoc) {
+                $args[$key] = "<span class=\"number\">$key</span> => $args[$key]";
+            }
+        }
+
+        return implode(', ', $args);
+    }
+
+    /**
+     * Returns human-readable exception name.
+     * @param \Exception $exception
+     * @return string|null human-readable exception name or null if it cannot be determined
+     */
+    public function getExceptionName($exception)
+    {
+        if ($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\InvalidCallException || $exception instanceof \yii\base\InvalidParamException || $exception instanceof \yii\base\UnknownMethodException) {
+            return $exception->getName();
+        }
+
+        return null;
+    }
+
+    /**
+     * @return bool if simple HTML should be rendered
+     * @since 2.0.12
+     */
+    protected function shouldRenderSimpleHtml()
+    {
+        return YII_ENV_TEST || Yii::$app->request->getIsAjax();
+    }
+}

+ 34 - 0
vendor/yiisoft/yii2/web/ForbiddenHttpException.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * ForbiddenHttpException represents a "Forbidden" HTTP exception with status code 403.
+ *
+ * Use this exception when a user is not allowed to perform the requested action.
+ * Using different credentials might or might not allow performing the requested action.
+ * If you do not want to expose authorization information to the user, it is valid
+ * to respond with a 404 [[NotFoundHttpException]].
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.3
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class ForbiddenHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(403, $message, $code, $previous);
+    }
+}

+ 34 - 0
vendor/yiisoft/yii2/web/GoneHttpException.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * GoneHttpException represents a "Gone" HTTP exception with status code 410.
+ *
+ * Throw a GoneHttpException when a user requests a resource that no longer exists
+ * at the requested url. For example, after a record is deleted, future requests
+ * for that record should return a 410 GoneHttpException instead of a 404
+ * [[NotFoundHttpException]].
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.9
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class GoneHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(410, $message, $code, $previous);
+    }
+}

+ 144 - 0
vendor/yiisoft/yii2/web/GroupUrlRule.php

@@ -0,0 +1,144 @@
+<?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\InvalidConfigException;
+
+/**
+ * GroupUrlRule represents a collection of URL rules sharing the same prefix in their patterns and routes.
+ *
+ * GroupUrlRule is best used by a module which often uses module ID as the prefix for the URL rules.
+ * For example, the following code creates a rule for the `admin` module:
+ *
+ * ```php
+ * new GroupUrlRule([
+ *     'prefix' => 'admin',
+ *     'rules' => [
+ *         'login' => 'user/login',
+ *         'logout' => 'user/logout',
+ *         'dashboard' => 'default/dashboard',
+ *     ],
+ * ]);
+ *
+ * // the above rule is equivalent to the following three rules:
+ *
+ * [
+ *     'admin/login' => 'admin/user/login',
+ *     'admin/logout' => 'admin/user/logout',
+ *     'admin/dashboard' => 'admin/default/dashboard',
+ * ]
+ * ```
+ *
+ * The above example assumes the prefix for patterns and routes are the same. They can be made different
+ * by configuring [[prefix]] and [[routePrefix]] separately.
+ *
+ * Using a GroupUrlRule is more efficient than directly declaring the individual rules it contains.
+ * This is because GroupUrlRule can quickly determine if it should process a URL parsing or creation request
+ * by simply checking if the prefix matches.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class GroupUrlRule extends CompositeUrlRule
+{
+    /**
+     * @var array the rules contained within this composite rule. Please refer to [[UrlManager::rules]]
+     * for the format of this property.
+     * @see prefix
+     * @see routePrefix
+     */
+    public $rules = [];
+    /**
+     * @var string the prefix for the pattern part of every rule declared in [[rules]].
+     * The prefix and the pattern will be separated with a slash.
+     */
+    public $prefix;
+    /**
+     * @var string the prefix for the route part of every rule declared in [[rules]].
+     * The prefix and the route will be separated with a slash.
+     * If this property is not set, it will take the value of [[prefix]].
+     */
+    public $routePrefix;
+    /**
+     * @var array the default configuration of URL rules. Individual rule configurations
+     * specified via [[rules]] will take precedence when the same property of the rule is configured.
+     */
+    public $ruleConfig = ['class' => 'yii\web\UrlRule'];
+
+
+    /**
+     * {@inheritdoc}
+     */
+    public function init()
+    {
+        $this->prefix = trim($this->prefix, '/');
+        $this->routePrefix = $this->routePrefix === null ? $this->prefix : trim($this->routePrefix, '/');
+        parent::init();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function createRules()
+    {
+        $rules = [];
+        foreach ($this->rules as $key => $rule) {
+            if (!is_array($rule)) {
+                $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
+                $verb = null;
+                if (preg_match("/^((?:(?:$verbs),)*(?:$verbs))\\s+(.*)$/", $key, $matches)) {
+                    $verb = explode(',', $matches[1]);
+                    $key = $matches[2];
+                }
+                $rule = [
+                    'pattern' => ltrim($this->prefix . '/' . $key, '/'),
+                    'route' => ltrim($this->routePrefix . '/' . $rule, '/'),
+                    'verb' => $verb
+                ];
+            } elseif (isset($rule['pattern'], $rule['route'])) {
+                $rule['pattern'] = ltrim($this->prefix . '/' . $rule['pattern'], '/');
+                $rule['route'] = ltrim($this->routePrefix . '/' . $rule['route'], '/');
+            }
+
+            $rule = Yii::createObject(array_merge($this->ruleConfig, $rule));
+            if (!$rule instanceof UrlRuleInterface) {
+                throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.');
+            }
+            $rules[] = $rule;
+        }
+
+        return $rules;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function parseRequest($manager, $request)
+    {
+        $pathInfo = $request->getPathInfo();
+        if ($this->prefix === '' || strpos($pathInfo . '/', $this->prefix . '/') === 0) {
+            return parent::parseRequest($manager, $request);
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createUrl($manager, $route, $params)
+    {
+        if ($this->routePrefix === '' || strpos($route, $this->routePrefix . '/') === 0) {
+            return parent::createUrl($manager, $route, $params);
+        }
+
+        $this->createStatus = UrlRule::CREATE_STATUS_ROUTE_MISMATCH;
+        return false;
+    }
+}

+ 235 - 0
vendor/yiisoft/yii2/web/HeaderCollection.php

@@ -0,0 +1,235 @@
+<?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-read int $count The number of headers in the collection. This property is read-only.
+ * @property-read \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|null 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);
+    }
+}

+ 29 - 0
vendor/yiisoft/yii2/web/HeadersAlreadySentException.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\Exception;
+
+/**
+ * HeadersAlreadySentException represents an exception caused by
+ * any headers that were already sent before web response was sent.
+ *
+ * @author Dmitry Dorogin <dmirogin@ya.ru>
+ * @since 2.0.14
+ */
+class HeadersAlreadySentException extends Exception
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct($file, $line)
+    {
+        $message = YII_DEBUG ? "Headers already sent in {$file} on line {$line}." : 'Headers already sent.';
+        parent::__construct($message);
+    }
+}

+ 42 - 0
vendor/yiisoft/yii2/web/HtmlResponseFormatter.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\Component;
+
+/**
+ * HtmlResponseFormatter formats the given data into an HTML response content.
+ *
+ * It is used by [[Response]] to format response data.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class HtmlResponseFormatter extends Component implements ResponseFormatterInterface
+{
+    /**
+     * @var string the Content-Type header for the response
+     */
+    public $contentType = 'text/html';
+
+
+    /**
+     * Formats the specified response.
+     * @param Response $response the response to be formatted.
+     */
+    public function format($response)
+    {
+        if (stripos($this->contentType, 'charset') === false) {
+            $this->contentType .= '; charset=' . $response->charset;
+        }
+        $response->getHeaders()->set('Content-Type', $this->contentType);
+        if ($response->data !== null) {
+            $response->content = $response->data;
+        }
+    }
+}

+ 62 - 0
vendor/yiisoft/yii2/web/HttpException.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\UserException;
+
+/**
+ * HttpException represents an exception caused by an improper request of the end-user.
+ *
+ * HttpException can be differentiated via its [[statusCode]] property value which
+ * keeps a standard HTTP status code (e.g. 404, 500). Error handlers may use this status code
+ * to decide how to format the error page.
+ *
+ * Throwing an HttpException like in the following example will result in the 404 page to be displayed.
+ *
+ * ```php
+ * if ($item === null) { // item does not exist
+ *     throw new \yii\web\HttpException(404, 'The requested Item could not be found.');
+ * }
+ * ```
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class HttpException extends UserException
+{
+    /**
+     * @var int HTTP status code, such as 403, 404, 500, etc.
+     */
+    public $statusCode;
+
+
+    /**
+     * Constructor.
+     * @param int $status HTTP status code, such as 404, 500, etc.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($status, $message = null, $code = 0, \Exception $previous = null)
+    {
+        $this->statusCode = $status;
+        parent::__construct($message, $code, $previous);
+    }
+
+    /**
+     * @return string the user-friendly name of this exception
+     */
+    public function getName()
+    {
+        if (isset(Response::$httpStatuses[$this->statusCode])) {
+            return Response::$httpStatuses[$this->statusCode];
+        }
+
+        return 'Error';
+    }
+}

+ 110 - 0
vendor/yiisoft/yii2/web/IdentityInterface.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * IdentityInterface is the interface that should be implemented by a class providing identity information.
+ *
+ * This interface can typically be implemented by a user model class. For example, the following
+ * code shows how to implement this interface by a User ActiveRecord class:
+ *
+ * ```php
+ * class User extends ActiveRecord implements IdentityInterface
+ * {
+ *     public static function findIdentity($id)
+ *     {
+ *         return static::findOne($id);
+ *     }
+ *
+ *     public static function findIdentityByAccessToken($token, $type = null)
+ *     {
+ *         return static::findOne(['access_token' => $token]);
+ *     }
+ *
+ *     public function getId()
+ *     {
+ *         return $this->id;
+ *     }
+ *
+ *     public function getAuthKey()
+ *     {
+ *         return $this->authKey;
+ *     }
+ *
+ *     public function validateAuthKey($authKey)
+ *     {
+ *         return $this->authKey === $authKey;
+ *     }
+ * }
+ * ```
+ *
+ * In some situations not all of these methods are required to be implemented.
+ * For example, if your application is a pure stateless RESTful application,
+ * you would only need to implement [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]
+ * and [[yii\web\IdentityInterface::getId()|getId()]] while leaving all other methods with an empty body.
+ * Or if your application uses session only authentication, you would need to implement all the methods
+ * except [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]].
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+interface IdentityInterface
+{
+    /**
+     * Finds an identity by the given ID.
+     * @param string|int $id the ID to be looked for
+     * @return IdentityInterface|null the identity object that matches the given ID.
+     * Null should be returned if such an identity cannot be found
+     * or the identity is not in an active state (disabled, deleted, etc.)
+     */
+    public static function findIdentity($id);
+
+    /**
+     * Finds an identity by the given token.
+     * @param mixed $token the token to be looked for
+     * @param mixed $type the type of the token. The value of this parameter depends on the implementation.
+     * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`.
+     * @return IdentityInterface|null the identity object that matches the given token.
+     * Null should be returned if such an identity cannot be found
+     * or the identity is not in an active state (disabled, deleted, etc.)
+     */
+    public static function findIdentityByAccessToken($token, $type = null);
+
+    /**
+     * Returns an ID that can uniquely identify a user identity.
+     * @return string|int an ID that uniquely identifies a user identity.
+     */
+    public function getId();
+
+    /**
+     * Returns a key that can be used to check the validity of a given identity ID.
+     *
+     * The key should be unique for each individual user, and should be persistent
+     * so that it can be used to check the validity of the user identity.
+     *
+     * The space of such keys should be big enough to defeat potential identity attacks.
+     *
+     * The returned key is used to validate session and auto-login (if [[User::enableAutoLogin]] is enabled).
+     *
+     * Make sure to invalidate earlier issued authKeys when you implement force user logout, password change and
+     * other scenarios, that require forceful access revocation for old sessions.
+     *
+     * @return string|null a key that is used to check the validity of a given identity ID.
+     * @see validateAuthKey()
+     */
+    public function getAuthKey();
+
+    /**
+     * Validates the given auth key.
+     *
+     * @param string $authKey the given auth key
+     * @return bool|null whether the given auth key is valid.
+     * @see getAuthKey()
+     */
+    public function validateAuthKey($authKey);
+}

+ 22 - 0
vendor/yiisoft/yii2/web/JqueryAsset.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * This asset bundle provides the [jQuery](http://jquery.com/) JavaScript library.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class JqueryAsset extends AssetBundle
+{
+    public $sourcePath = '@bower/jquery/dist';
+    public $js = [
+        'jquery.js',
+    ];
+}

+ 48 - 0
vendor/yiisoft/yii2/web/JsExpression.php

@@ -0,0 +1,48 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\BaseObject;
+
+/**
+ * `JsExpression` marks a string as a JavaScript expression.
+ *
+ * When using [[\yii\helpers\Json::encode()]] or [[\yii\helpers\Json::htmlEncode()]] to encode a value, `JsExpression` objects
+ * will be specially handled and encoded as a JavaScript expression instead of a string.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class JsExpression extends BaseObject
+{
+    /**
+     * @var string the JavaScript expression represented by this object
+     */
+    public $expression;
+
+
+    /**
+     * Constructor.
+     * @param string $expression the JavaScript expression represented by this object
+     * @param array $config additional configurations for this object
+     */
+    public function __construct($expression, $config = [])
+    {
+        $this->expression = $expression;
+        parent::__construct($config);
+    }
+
+    /**
+     * The PHP magic function converting an object into a string.
+     * @return string the JavaScript expression.
+     */
+    public function __toString()
+    {
+        return (string) $this->expression;
+    }
+}

+ 66 - 0
vendor/yiisoft/yii2/web/JsonParser.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\InvalidArgumentException;
+use yii\helpers\Json;
+
+/**
+ * Parses a raw HTTP request using [[\yii\helpers\Json::decode()]].
+ *
+ * To enable parsing for JSON requests you can configure [[Request::parsers]] using this class:
+ *
+ * ```php
+ * 'request' => [
+ *     'parsers' => [
+ *         'application/json' => 'yii\web\JsonParser',
+ *     ]
+ * ]
+ * ```
+ *
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class JsonParser implements RequestParserInterface
+{
+    /**
+     * @var bool whether to return objects in terms of associative arrays.
+     */
+    public $asArray = true;
+    /**
+     * @var bool whether to throw a [[BadRequestHttpException]] if the body is invalid JSON
+     */
+    public $throwException = true;
+
+
+    /**
+     * Parses a HTTP request body.
+     * @param string $rawBody the raw HTTP request body.
+     * @param string $contentType the content type specified for the request body.
+     * @return array|\stdClass parameters parsed from the request body
+     * @throws BadRequestHttpException if the body contains invalid json and [[throwException]] is `true`.
+     */
+    public function parse($rawBody, $contentType)
+    {
+        // converts JSONP to JSON
+        if (strpos($contentType, 'application/javascript') !== false) {
+            $rawBody = preg_filter('/(^[^{]+|[^}]+$)/', '', $rawBody);
+        }
+
+        try {
+            $parameters = Json::decode($rawBody, $this->asArray);
+            return $parameters === null ? [] : $parameters;
+        } catch (InvalidArgumentException $e) {
+            if ($this->throwException) {
+                throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage());
+            }
+
+            return [];
+        }
+    }
+}

+ 147 - 0
vendor/yiisoft/yii2/web/JsonResponseFormatter.php

@@ -0,0 +1,147 @@
+<?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\Component;
+use yii\helpers\Json;
+
+/**
+ * JsonResponseFormatter formats the given data into a JSON or JSONP response content.
+ *
+ * It is used by [[Response]] to format response data.
+ *
+ * To configure properties like [[encodeOptions]] or [[prettyPrint]], you can configure the `response`
+ * application component like the following:
+ *
+ * ```php
+ * 'response' => [
+ *     // ...
+ *     'formatters' => [
+ *         \yii\web\Response::FORMAT_JSON => [
+ *              'class' => 'yii\web\JsonResponseFormatter',
+ *              'prettyPrint' => YII_DEBUG, // use "pretty" output in debug mode
+ *              // ...
+ *         ],
+ *     ],
+ * ],
+ * ```
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class JsonResponseFormatter extends Component implements ResponseFormatterInterface
+{
+    /**
+     * JSON Content Type
+     * @since 2.0.14
+     */
+    const CONTENT_TYPE_JSONP = 'application/javascript; charset=UTF-8';
+    /**
+     * JSONP Content Type
+     * @since 2.0.14
+     */
+    const CONTENT_TYPE_JSON = 'application/json; charset=UTF-8';
+    /**
+     * HAL JSON Content Type
+     * @since 2.0.14
+     */
+    const CONTENT_TYPE_HAL_JSON = 'application/hal+json; charset=UTF-8';
+
+    /**
+     * @var string|null custom value of the `Content-Type` header of the response.
+     * When equals `null` default content type will be used based on the `useJsonp` property.
+     * @since 2.0.14
+     */
+    public $contentType;
+    /**
+     * @var bool whether to use JSONP response format. When this is true, the [[Response::data|response data]]
+     * must be an array consisting of `data` and `callback` members. The latter should be a JavaScript
+     * function name while the former will be passed to this function as a parameter.
+     */
+    public $useJsonp = false;
+    /**
+     * @var int the encoding options passed to [[Json::encode()]]. For more details please refer to
+     * <https://secure.php.net/manual/en/function.json-encode.php>.
+     * Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`.
+     * This property has no effect, when [[useJsonp]] is `true`.
+     * @since 2.0.7
+     */
+    public $encodeOptions = 320;
+    /**
+     * @var bool whether to format the output in a readable "pretty" format. This can be useful for debugging purpose.
+     * If this is true, `JSON_PRETTY_PRINT` will be added to [[encodeOptions]].
+     * Defaults to `false`.
+     * This property has no effect, when [[useJsonp]] is `true`.
+     * @since 2.0.7
+     */
+    public $prettyPrint = false;
+
+
+    /**
+     * Formats the specified response.
+     * @param Response $response the response to be formatted.
+     */
+    public function format($response)
+    {
+        if ($this->contentType === null) {
+            $this->contentType = $this->useJsonp
+                ? self::CONTENT_TYPE_JSONP
+                : self::CONTENT_TYPE_JSON;
+        } elseif (strpos($this->contentType, 'charset') === false) {
+            $this->contentType .= '; charset=UTF-8';
+        }
+        $response->getHeaders()->set('Content-Type', $this->contentType);
+  
+        if ($this->useJsonp) {
+            $this->formatJsonp($response);
+        } else {
+            $this->formatJson($response);
+        }
+    }
+
+    /**
+     * Formats response data in JSON format.
+     * @param Response $response
+     */
+    protected function formatJson($response)
+    {
+        if ($response->data !== null) {
+            $options = $this->encodeOptions;
+            if ($this->prettyPrint) {
+                $options |= JSON_PRETTY_PRINT;
+            }
+            $response->content = Json::encode($response->data, $options);
+        } elseif ($response->content === null) {
+            $response->content = 'null';
+        }
+    }
+
+    /**
+     * Formats response data in JSONP format.
+     * @param Response $response
+     */
+    protected function formatJsonp($response)
+    {
+        if (is_array($response->data)
+            && isset($response->data['data'], $response->data['callback'])
+        ) {
+            $response->content = sprintf(
+                '%s(%s);',
+                $response->data['callback'],
+                Json::htmlEncode($response->data['data'])
+            );
+        } elseif ($response->data !== null) {
+            $response->content = '';
+            Yii::warning(
+                "The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.",
+                __METHOD__
+            );
+        }
+    }
+}

+ 76 - 0
vendor/yiisoft/yii2/web/Link.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\BaseObject;
+
+/**
+ * Link represents a link object as defined in [JSON Hypermedia API Language](https://tools.ietf.org/html/draft-kelly-json-hal-03).
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Link extends BaseObject
+{
+    /**
+     * The self link.
+     */
+    const REL_SELF = 'self';
+
+    /**
+     * @var string a URI [RFC3986](https://tools.ietf.org/html/rfc3986) or
+     * URI template [RFC6570](https://tools.ietf.org/html/rfc6570). This property is required.
+     */
+    public $href;
+    /**
+     * @var string a secondary key for selecting Link Objects which share the same relation type
+     */
+    public $name;
+    /**
+     * @var string a hint to indicate the media type expected when dereferencing the target resource
+     */
+    public $type;
+    /**
+     * @var bool a value indicating whether [[href]] refers to a URI or URI template.
+     */
+    public $templated = false;
+    /**
+     * @var string a URI that hints about the profile of the target resource.
+     */
+    public $profile;
+    /**
+     * @var string a label describing the link
+     */
+    public $title;
+    /**
+     * @var string the language of the target resource
+     */
+    public $hreflang;
+
+
+    /**
+     * Serializes a list of links into proper array format.
+     * @param array $links the links to be serialized
+     * @return array the proper array representation of the links.
+     */
+    public static function serialize(array $links)
+    {
+        foreach ($links as $rel => $link) {
+            if (is_array($link)) {
+                foreach ($link as $i => $l) {
+                    $link[$i] = $l instanceof self ? array_filter((array) $l) : ['href' => $l];
+                }
+                $links[$rel] = $link;
+            } elseif (!$link instanceof self) {
+                $links[$rel] = ['href' => $link];
+            }
+        }
+
+        return $links;
+    }
+}

+ 42 - 0
vendor/yiisoft/yii2/web/Linkable.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * Linkable is the interface that should be implemented by classes that typically represent locatable resources.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+interface Linkable
+{
+    /**
+     * Returns a list of links.
+     *
+     * Each link is either a URI or a [[Link]] object. The return value of this method should
+     * be an array whose keys are the relation names and values the corresponding links.
+     *
+     * If a relation name corresponds to multiple links, use an array to represent them.
+     *
+     * For example,
+     *
+     * ```php
+     * [
+     *     'self' => 'http://example.com/users/1',
+     *     'friends' => [
+     *         'http://example.com/users/2',
+     *         'http://example.com/users/3',
+     *     ],
+     *     'manager' => $managerLink, // $managerLink is a Link object
+     * ]
+     * ```
+     *
+     * @return array the links
+     */
+    public function getLinks();
+}

+ 29 - 0
vendor/yiisoft/yii2/web/MethodNotAllowedHttpException.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * MethodNotAllowedHttpException represents a "Method Not Allowed" HTTP exception with status code 405.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.5
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class MethodNotAllowedHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(405, $message, $code, $previous);
+    }
+}

+ 131 - 0
vendor/yiisoft/yii2/web/MultiFieldSession.php

@@ -0,0 +1,131 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * MultiFieldSession is the base class for session storage implementations with multi-field data storage support.
+ *
+ * With multi-field data storage, session data can be split between several fields in the storage record.
+ * Using such a storage allows saving particular session data into separated field, which then can be used
+ * to manipulate sessions in the way plain PHP does not allow.
+ *
+ * For example the ID of the authenticated user can be saved as separated column in the MySQL 'session' table,
+ * which allows to query all active sessions for a particular user or terminate them at will.
+ *
+ * Customizing of the session writing is performed via [[writeCallback]], reading via [[readCallback]].
+ *
+ * While extending this class you should use [[composeFields()]] method - while writing the session data into the storage and
+ * [[extractData()]] - while reading session data from the storage.
+ *
+ * @property-read bool $useCustomStorage Whether to use custom storage. This property is read-only.
+ *
+ * @author Paul Klimov <klimov.paul@gmail.com>
+ * @since 2.0.6
+ */
+abstract class MultiFieldSession extends Session
+{
+    /**
+     * @var callable a callback that will be called during session data reading.
+     * The signature of the callback should be as follows:
+     *
+     * ```
+     * function ($fields)
+     * ```
+     *
+     * where `$fields` is the storage field set for read session and `$session` is this session instance.
+     * If callback returns an array, it will be merged into the session data.
+     *
+     * For example:
+     *
+     * ```php
+     * function ($fields) {
+     *     return [
+     *         'expireDate' => Yii::$app->formatter->asDate($fields['expire']),
+     *     ];
+     * }
+     * ```
+     */
+    public $readCallback;
+    /**
+     * @var callable a callback that will be called during session data writing.
+     * The signature of the callback should be as follows:
+     *
+     * ```
+     * function ($session)
+     * ```
+     *
+     * where `$session` is this session instance, this variable can be used to retrieve session data.
+     * Callback should return the actual fields set, which should be saved into the session storage.
+     *
+     * For example:
+     *
+     * ```php
+     * function ($session) {
+     *     return [
+     *         'user_id' => Yii::$app->user->id,
+     *         'ip' => $_SERVER['REMOTE_ADDR'],
+     *         'is_trusted' => $session->get('is_trusted', false),
+     *     ];
+     * }
+     * ```
+     */
+    public $writeCallback;
+
+
+    /**
+     * Returns a value indicating whether to use custom session storage.
+     * This method overrides the parent implementation and always returns true.
+     * @return bool whether to use custom storage.
+     */
+    public function getUseCustomStorage()
+    {
+        return true;
+    }
+
+    /**
+     * Composes storage field set for session writing.
+     * @param string $id Optional session id
+     * @param string $data Optional session data
+     * @return array storage fields
+     */
+    protected function composeFields($id = null, $data = null)
+    {
+        $fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : [];
+        if ($id !== null) {
+            $fields['id'] = $id;
+        }
+        if ($data !== null) {
+            $fields['data'] = $data;
+        }
+        return $fields;
+    }
+
+    /**
+     * Extracts session data from storage field set.
+     * @param array $fields storage fields.
+     * @return string session data.
+     */
+    protected function extractData($fields)
+    {
+        if ($this->readCallback !== null) {
+            if (!isset($fields['data'])) {
+                $fields['data'] = '';
+            }
+            $extraData = call_user_func($this->readCallback, $fields);
+            if (!empty($extraData)) {
+                session_decode($fields['data']);
+                $_SESSION = array_merge((array) $_SESSION, (array) $extraData);
+                return session_encode();
+            }
+
+            return $fields['data'];
+        }
+
+        return isset($fields['data']) ? $fields['data'] : '';
+    }
+}

+ 372 - 0
vendor/yiisoft/yii2/web/MultipartFormDataParser.php

@@ -0,0 +1,372 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\BaseObject;
+use yii\helpers\ArrayHelper;
+use yii\helpers\StringHelper;
+
+/**
+ * MultipartFormDataParser parses content encoded as 'multipart/form-data'.
+ * This parser provides the fallback for the 'multipart/form-data' processing on non POST requests,
+ * for example: the one with 'PUT' request method.
+ *
+ * In order to enable this parser you should configure [[Request::parsers]] in the following way:
+ *
+ * ```php
+ * return [
+ *     'components' => [
+ *         'request' => [
+ *             'parsers' => [
+ *                 'multipart/form-data' => 'yii\web\MultipartFormDataParser'
+ *             ],
+ *         ],
+ *         // ...
+ *     ],
+ *     // ...
+ * ];
+ * ```
+ *
+ * Method [[parse()]] of this parser automatically populates `$_FILES` with the files parsed from raw body.
+ *
+ * > Note: since this is a request parser, it will initialize `$_FILES` values on [[Request::getBodyParams()]].
+ * Until this method is invoked, `$_FILES` array will remain empty even if there are submitted files in the
+ * request body. Make sure you have requested body params before any attempt to get uploaded file in case
+ * you are using this parser.
+ *
+ * Usage example:
+ *
+ * ```php
+ * use yii\web\UploadedFile;
+ *
+ * $restRequestData = Yii::$app->request->getBodyParams();
+ * $uploadedFile = UploadedFile::getInstancesByName('photo');
+ *
+ * $model = new Item();
+ * $model->populate($restRequestData);
+ * copy($uploadedFile->tempName, '/path/to/file/storage/photo.jpg');
+ * ```
+ *
+ * > Note: although this parser fully emulates regular structure of the `$_FILES`, related temporary
+ * files, which are available via `tmp_name` key, will not be recognized by PHP as uploaded ones.
+ * Thus functions like `is_uploaded_file()` and `move_uploaded_file()` will fail on them.
+ *
+ * @property int $uploadFileMaxCount Maximum upload files count.
+ * @property int $uploadFileMaxSize Upload file max size in bytes.
+ *
+ * @author Paul Klimov <klimov.paul@gmail.com>
+ * @since 2.0.10
+ */
+class MultipartFormDataParser extends BaseObject implements RequestParserInterface
+{
+    /**
+     * @var bool whether to parse raw body even for 'POST' request and `$_FILES` already populated.
+     * By default this option is disabled saving performance for 'POST' requests, which are already
+     * processed by PHP automatically.
+     * > Note: if this option is enabled, value of `$_FILES` will be reset on each parse.
+     * @since 2.0.13
+     */
+    public $force = false;
+
+    /**
+     * @var int upload file max size in bytes.
+     */
+    private $_uploadFileMaxSize;
+    /**
+     * @var int maximum upload files count.
+     */
+    private $_uploadFileMaxCount;
+
+
+    /**
+     * @return int upload file max size in bytes.
+     */
+    public function getUploadFileMaxSize()
+    {
+        if ($this->_uploadFileMaxSize === null) {
+            $this->_uploadFileMaxSize = $this->getByteSize(ini_get('upload_max_filesize'));
+        }
+
+        return $this->_uploadFileMaxSize;
+    }
+
+    /**
+     * @param int $uploadFileMaxSize upload file max size in bytes.
+     */
+    public function setUploadFileMaxSize($uploadFileMaxSize)
+    {
+        $this->_uploadFileMaxSize = $uploadFileMaxSize;
+    }
+
+    /**
+     * @return int maximum upload files count.
+     */
+    public function getUploadFileMaxCount()
+    {
+        if ($this->_uploadFileMaxCount === null) {
+            $this->_uploadFileMaxCount = (int)ini_get('max_file_uploads');
+        }
+
+        return $this->_uploadFileMaxCount;
+    }
+
+    /**
+     * @param int $uploadFileMaxCount maximum upload files count.
+     */
+    public function setUploadFileMaxCount($uploadFileMaxCount)
+    {
+        $this->_uploadFileMaxCount = $uploadFileMaxCount;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function parse($rawBody, $contentType)
+    {
+        if (!$this->force) {
+            if (!empty($_POST) || !empty($_FILES)) {
+                // normal POST request is parsed by PHP automatically
+                return $_POST;
+            }
+        } else {
+            $_FILES = [];
+        }
+
+        if (empty($rawBody)) {
+            return [];
+        }
+
+        if (!preg_match('/boundary="?(.*)"?$/is', $contentType, $matches)) {
+            return [];
+        }
+
+        $boundary = trim($matches[1], '"');
+
+        $bodyParts = preg_split('/\\R?-+' . preg_quote($boundary, '/') . '/s', $rawBody);
+        array_pop($bodyParts); // last block always has no data, contains boundary ending like `--`
+
+        $bodyParams = [];
+        $filesCount = 0;
+        foreach ($bodyParts as $bodyPart) {
+            if (empty($bodyPart)) {
+                continue;
+            }
+            list($headers, $value) = preg_split('/\\R\\R/', $bodyPart, 2);
+            $headers = $this->parseHeaders($headers);
+
+            if (!isset($headers['content-disposition']['name'])) {
+                continue;
+            }
+
+            if (isset($headers['content-disposition']['filename'])) {
+                // file upload:
+                if ($filesCount >= $this->getUploadFileMaxCount()) {
+                    continue;
+                }
+
+                $fileInfo = [
+                    'name' => $headers['content-disposition']['filename'],
+                    'type' => ArrayHelper::getValue($headers, 'content-type', 'application/octet-stream'),
+                    'size' => StringHelper::byteLength($value),
+                    'error' => UPLOAD_ERR_OK,
+                    'tmp_name' => null,
+                ];
+
+                if ($fileInfo['size'] > $this->getUploadFileMaxSize()) {
+                    $fileInfo['error'] = UPLOAD_ERR_INI_SIZE;
+                } else {
+                    $tmpResource = tmpfile();
+                    if ($tmpResource === false) {
+                        $fileInfo['error'] = UPLOAD_ERR_CANT_WRITE;
+                    } else {
+                        $tmpResourceMetaData = stream_get_meta_data($tmpResource);
+                        $tmpFileName = $tmpResourceMetaData['uri'];
+                        if (empty($tmpFileName)) {
+                            $fileInfo['error'] = UPLOAD_ERR_CANT_WRITE;
+                            @fclose($tmpResource);
+                        } else {
+                            fwrite($tmpResource, $value);
+                            rewind($tmpResource);
+                            $fileInfo['tmp_name'] = $tmpFileName;
+                            $fileInfo['tmp_resource'] = $tmpResource; // save file resource, otherwise it will be deleted
+                        }
+                    }
+                }
+
+                $this->addFile($_FILES, $headers['content-disposition']['name'], $fileInfo);
+
+                $filesCount++;
+            } else {
+                // regular parameter:
+                $this->addValue($bodyParams, $headers['content-disposition']['name'], $value);
+            }
+        }
+
+        return $bodyParams;
+    }
+
+    /**
+     * Parses content part headers.
+     * @param string $headerContent headers source content
+     * @return array parsed headers.
+     */
+    private function parseHeaders($headerContent)
+    {
+        $headers = [];
+        $headerParts = preg_split('/\\R/su', $headerContent, -1, PREG_SPLIT_NO_EMPTY);
+        foreach ($headerParts as $headerPart) {
+            if (strpos($headerPart, ':') === false) {
+                continue;
+            }
+
+            list($headerName, $headerValue) = explode(':', $headerPart, 2);
+            $headerName = strtolower(trim($headerName));
+            $headerValue = trim($headerValue);
+
+            if (strpos($headerValue, ';') === false) {
+                $headers[$headerName] = $headerValue;
+            } else {
+                $headers[$headerName] = [];
+                foreach (explode(';', $headerValue) as $part) {
+                    $part = trim($part);
+                    if (strpos($part, '=') === false) {
+                        $headers[$headerName][] = $part;
+                    } else {
+                        list($name, $value) = explode('=', $part, 2);
+                        $name = strtolower(trim($name));
+                        $value = trim(trim($value), '"');
+                        $headers[$headerName][$name] = $value;
+                    }
+                }
+            }
+        }
+
+        return $headers;
+    }
+
+    /**
+     * Adds value to the array by input name, e.g. `Item[name]`.
+     * @param array $array array which should store value.
+     * @param string $name input name specification.
+     * @param mixed $value value to be added.
+     */
+    private function addValue(&$array, $name, $value)
+    {
+        $nameParts = preg_split('/\\]\\[|\\[/s', $name);
+        $current = &$array;
+        foreach ($nameParts as $namePart) {
+            $namePart = trim($namePart, ']');
+            if ($namePart === '') {
+                $current[] = [];
+                $keys = array_keys($current);
+                $lastKey = array_pop($keys);
+                $current = &$current[$lastKey];
+            } else {
+                if (!isset($current[$namePart])) {
+                    $current[$namePart] = [];
+                }
+                $current = &$current[$namePart];
+            }
+        }
+        $current = $value;
+    }
+
+    /**
+     * Adds file info to the uploaded files array by input name, e.g. `Item[file]`.
+     * @param array $files array containing uploaded files
+     * @param string $name input name specification.
+     * @param array $info file info.
+     */
+    private function addFile(&$files, $name, $info)
+    {
+        if (strpos($name, '[') === false) {
+            $files[$name] = $info;
+            return;
+        }
+
+        $fileInfoAttributes = [
+            'name',
+            'type',
+            'size',
+            'error',
+            'tmp_name',
+            'tmp_resource',
+        ];
+
+        $nameParts = preg_split('/\\]\\[|\\[/s', $name);
+        $baseName = array_shift($nameParts);
+        if (!isset($files[$baseName])) {
+            $files[$baseName] = [];
+            foreach ($fileInfoAttributes as $attribute) {
+                $files[$baseName][$attribute] = [];
+            }
+        } else {
+            foreach ($fileInfoAttributes as $attribute) {
+                $files[$baseName][$attribute] = (array) $files[$baseName][$attribute];
+            }
+        }
+
+        foreach ($fileInfoAttributes as $attribute) {
+            if (!isset($info[$attribute])) {
+                continue;
+            }
+
+            $current = &$files[$baseName][$attribute];
+            foreach ($nameParts as $namePart) {
+                $namePart = trim($namePart, ']');
+                if ($namePart === '') {
+                    $current[] = [];
+                    $keys = array_keys($current);
+                    $lastKey = array_pop($keys);
+                    $current = &$current[$lastKey];
+                } else {
+                    if (!isset($current[$namePart])) {
+                        $current[$namePart] = [];
+                    }
+                    $current = &$current[$namePart];
+                }
+            }
+            $current = $info[$attribute];
+        }
+    }
+
+    /**
+     * Gets the size in bytes from verbose size representation.
+     *
+     * For example: '5K' => 5*1024.
+     * @param string $verboseSize verbose size representation.
+     * @return int actual size in bytes.
+     */
+    private function getByteSize($verboseSize)
+    {
+        if (empty($verboseSize)) {
+            return 0;
+        }
+        if (is_numeric($verboseSize)) {
+            return (int) $verboseSize;
+        }
+        $sizeUnit = trim($verboseSize, '0123456789');
+        $size = trim(str_replace($sizeUnit, '', $verboseSize));
+        if (!is_numeric($size)) {
+            return 0;
+        }
+        switch (strtolower($sizeUnit)) {
+            case 'kb':
+            case 'k':
+                return $size * 1024;
+            case 'mb':
+            case 'm':
+                return $size * 1024 * 1024;
+            case 'gb':
+            case 'g':
+                return $size * 1024 * 1024 * 1024;
+            default:
+                return 0;
+        }
+    }
+}

+ 33 - 0
vendor/yiisoft/yii2/web/NotAcceptableHttpException.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * NotAcceptableHttpException represents a "Not Acceptable" HTTP exception with status code 406.
+ *
+ * Use this exception when the client requests a Content-Type that your
+ * application cannot return. Note that, according to the HTTP 1.1 specification,
+ * you are not required to respond with this status code in this situation.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.6
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class NotAcceptableHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(406, $message, $code, $previous);
+    }
+}

+ 29 - 0
vendor/yiisoft/yii2/web/NotFoundHttpException.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * NotFoundHttpException represents a "Not Found" HTTP exception with status code 404.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.4
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class NotFoundHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(404, $message, $code, $previous);
+    }
+}

+ 35 - 0
vendor/yiisoft/yii2/web/RangeNotSatisfiableHttpException.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * RangeNotSatisfiableHttpException represents an exception caused by an improper request of the end-user.
+ * This exception thrown when the requested range is not satisfiable: the client asked for a portion of
+ * the file (byte serving), but the server cannot supply that portion. For example, if the client asked for
+ * a part of the file that lies beyond the end of the file.
+ *
+ * Throwing an RangeNotSatisfiableHttpException like in the following example will result in the error page
+ * with error 416 to be displayed.
+ *
+ * @author Zalatov Alexander <CaHbKa.Z@gmail.com>
+ *
+ * @since 2.0.11
+ */
+class RangeNotSatisfiableHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(416, $message, $code, $previous);
+    }
+}

+ 1957 - 0
vendor/yiisoft/yii2/web/Request.php

@@ -0,0 +1,1957 @@
+<?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\InvalidConfigException;
+use yii\validators\IpValidator;
+
+/**
+ * The web Request class represents an HTTP request.
+ *
+ * It encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
+ * Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST
+ * parameters sent via other HTTP methods like PUT or DELETE.
+ *
+ * Request is configured as an application component in [[\yii\web\Application]] by default.
+ * You can access that instance via `Yii::$app->request`.
+ *
+ * For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
+ *
+ * @property-read string $absoluteUrl The currently requested absolute URL. This property is read-only.
+ * @property array $acceptableContentTypes The content types ordered by the quality score. Types with the
+ * highest scores will be returned first. The array keys are the content types, while the array values are the
+ * corresponding quality score and other parameters as given in the header.
+ * @property array $acceptableLanguages The languages ordered by the preference level. The first element
+ * represents the most preferred language.
+ * @property-read array $authCredentials That contains exactly two elements: - 0: the username sent via HTTP
+ * authentication, `null` if the username is not given - 1: the password sent via HTTP authentication, `null` if
+ * the password is not given. This property is read-only.
+ * @property-read string|null $authPassword The password sent via HTTP authentication, `null` if the password
+ * is not given. This property is read-only.
+ * @property-read string|null $authUser The username sent via HTTP authentication, `null` if the username is
+ * not given. This property is read-only.
+ * @property string $baseUrl The relative URL for the application.
+ * @property array $bodyParams The request parameters given in the request body.
+ * @property-read string $contentType Request content-type. Null is returned if this information is not
+ * available. This property is read-only.
+ * @property-read CookieCollection $cookies The cookie collection. This property is read-only.
+ * @property-read string $csrfToken The token used to perform CSRF validation. This property is read-only.
+ * @property-read string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is
+ * returned if no such header is sent. This property is read-only.
+ * @property-read array $eTags The entity tags. This property is read-only.
+ * @property-read HeaderCollection $headers The header collection. This property is read-only.
+ * @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
+ * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
+ * [[getHostInfo()]] for security related notes on this property.
+ * @property-read string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`). This
+ * property is read-only.
+ * @property-read bool $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only.
+ * @property-read bool $isDelete Whether this is a DELETE request. This property is read-only.
+ * @property-read bool $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is
+ * read-only.
+ * @property-read bool $isGet Whether this is a GET request. This property is read-only.
+ * @property-read bool $isHead Whether this is a HEAD request. This property is read-only.
+ * @property-read bool $isOptions Whether this is a OPTIONS request. This property is read-only.
+ * @property-read bool $isPatch Whether this is a PATCH request. This property is read-only.
+ * @property-read bool $isPjax Whether this is a PJAX request. This property is read-only.
+ * @property-read bool $isPost Whether this is a POST request. This property is read-only.
+ * @property-read bool $isPut Whether this is a PUT request. This property is read-only.
+ * @property-read bool $isSecureConnection If the request is sent via secure channel (https). This property is
+ * read-only.
+ * @property-read string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value
+ * returned is turned into upper case. This property is read-only.
+ * @property-read string|null $origin URL origin of a CORS request, `null` if not available. This property is
+ * read-only.
+ * @property string $pathInfo Part of the request URL that is after the entry script and before the question
+ * mark. Note, the returned path info is already URL-decoded.
+ * @property int $port Port number for insecure requests.
+ * @property array $queryParams The request GET parameter values.
+ * @property-read string $queryString Part of the request URL that is after the question mark. This property
+ * is read-only.
+ * @property string $rawBody The request body.
+ * @property-read string|null $referrer URL referrer, null if not available. This property is read-only.
+ * @property-read string|null $remoteHost Remote host name, `null` if not available. This property is
+ * read-only.
+ * @property-read string|null $remoteIP Remote IP address, `null` if not available. This property is
+ * read-only.
+ * @property string $scriptFile The entry script file path.
+ * @property string $scriptUrl The relative URL of the entry script.
+ * @property int $securePort Port number for secure requests.
+ * @property-read string $serverName Server name, null if not available. This property is read-only.
+ * @property-read int|null $serverPort Server port number, null if not available. This property is read-only.
+ * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
+ * depending on the client.
+ * @property-read string|null $userAgent User agent, null if not available. This property is read-only.
+ * @property-read string|null $userHost User host name, null if not available. This property is read-only.
+ * @property-read string|null $userIP User IP address, null if not available. This property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ * @SuppressWarnings(PHPMD.SuperGlobals)
+ */
+class Request extends \yii\base\Request
+{
+    /**
+     * The name of the HTTP header for sending CSRF token.
+     */
+    const CSRF_HEADER = 'X-CSRF-Token';
+    /**
+     * The length of the CSRF token mask.
+     * @deprecated since 2.0.12. The mask length is now equal to the token length.
+     */
+    const CSRF_MASK_LENGTH = 8;
+
+    /**
+     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
+     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
+     * from the same application. If not, a 400 HTTP exception will be raised.
+     *
+     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
+     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
+     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
+     *
+     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
+     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
+     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
+     *
+     * @see Controller::enableCsrfValidation
+     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
+     */
+    public $enableCsrfValidation = true;
+    /**
+     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
+     * This property is used only when [[enableCsrfValidation]] is true.
+     */
+    public $csrfParam = '_csrf';
+    /**
+     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
+     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
+     */
+    public $csrfCookie = ['httpOnly' => true];
+    /**
+     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
+     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
+     * security, it requires starting a session for every page, which will degrade your site performance.
+     */
+    public $enableCsrfCookie = true;
+    /**
+     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
+     */
+    public $enableCookieValidation = true;
+    /**
+     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
+     */
+    public $cookieValidationKey;
+    /**
+     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
+     * request tunneled through POST. Defaults to '_method'.
+     * @see getMethod()
+     * @see getBodyParams()
+     */
+    public $methodParam = '_method';
+    /**
+     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
+     * The array keys are the request `Content-Types`, and the array values are the
+     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
+     * A parser must implement the [[RequestParserInterface]].
+     *
+     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
+     *
+     * ```
+     * [
+     *     'application/json' => 'yii\web\JsonParser',
+     * ]
+     * ```
+     *
+     * To register a parser for parsing all request types you can use `'*'` as the array key.
+     * This one will be used as a fallback in case no other types match.
+     *
+     * @see getBodyParams()
+     */
+    public $parsers = [];
+    /**
+     * @var array the configuration for trusted security related headers.
+     *
+     * An array key is an IPv4 or IPv6 IP address in CIDR notation for matching a client.
+     *
+     * An array value is a list of headers to trust. These will be matched against
+     * [[secureHeaders]] to determine which headers are allowed to be sent by a specified host.
+     * The case of the header names must be the same as specified in [[secureHeaders]].
+     *
+     * For example, to trust all headers listed in [[secureHeaders]] for IP addresses
+     * in range `192.168.0.0-192.168.0.254` write the following:
+     *
+     * ```php
+     * [
+     *     '192.168.0.0/24',
+     * ]
+     * ```
+     *
+     * To trust just the `X-Forwarded-For` header from `10.0.0.1`, use:
+     *
+     * ```
+     * [
+     *     '10.0.0.1' => ['X-Forwarded-For']
+     * ]
+     * ```
+     *
+     * Default is to trust all headers except those listed in [[secureHeaders]] from all hosts.
+     * Matches are tried in order and searching is stopped when IP matches.
+     *
+     * > Info: Matching is performed using [[IpValidator]].
+     * See [[IpValidator::::setRanges()|IpValidator::setRanges()]]
+     * and [[IpValidator::networks]] for advanced matching.
+     *
+     * @see $secureHeaders
+     * @since 2.0.13
+     */
+    public $trustedHosts = [];
+    /**
+     * @var array lists of headers that are, by default, subject to the trusted host configuration.
+     * These headers will be filtered unless explicitly allowed in [[trustedHosts]].
+     * If the list contains the `Forwarded` header, processing will be done according to RFC 7239.
+     * The match of header names is case-insensitive.
+     * @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
+     * @see https://tools.ietf.org/html/rfc7239
+     * @see $trustedHosts
+     * @since 2.0.13
+     */
+    public $secureHeaders = [
+        // Common:
+        'X-Forwarded-For',
+        'X-Forwarded-Host',
+        'X-Forwarded-Proto',
+
+        // Microsoft:
+        'Front-End-Https',
+        'X-Rewrite-Url',
+
+        // ngrok:
+        'X-Original-Host',
+    ];
+    /**
+     * @var string[] List of headers where proxies store the real client IP.
+     * It's not advisable to put insecure headers here.
+     * To use the `Forwarded` header according to RFC 7239, the header must be added to [[secureHeaders]] list.
+     * The match of header names is case-insensitive.
+     * @see $trustedHosts
+     * @see $secureHeaders
+     * @since 2.0.13
+     */
+    public $ipHeaders = [
+        'X-Forwarded-For', // Common
+    ];
+    /**
+     * @var array list of headers to check for determining whether the connection is made via HTTPS.
+     * The array keys are header names and the array value is a list of header values that indicate a secure connection.
+     * The match of header names and values is case-insensitive.
+     * It's not advisable to put insecure headers here.
+     * @see $trustedHosts
+     * @see $secureHeaders
+     * @since 2.0.13
+     */
+    public $secureProtocolHeaders = [
+        'X-Forwarded-Proto' => ['https'], // Common
+        'Front-End-Https' => ['on'], // Microsoft
+    ];
+
+    /**
+     * @var CookieCollection Collection of request cookies.
+     */
+    private $_cookies;
+    /**
+     * @var HeaderCollection Collection of request headers.
+     */
+    private $_headers;
+
+
+    /**
+     * Resolves the current request into a route and the associated parameters.
+     * @return array the first element is the route, and the second is the associated parameters.
+     * @throws NotFoundHttpException if the request cannot be resolved.
+     */
+    public function resolve()
+    {
+        $result = Yii::$app->getUrlManager()->parseRequest($this);
+        if ($result !== false) {
+            list($route, $params) = $result;
+            if ($this->_queryParams === null) {
+                $_GET = $params + $_GET; // preserve numeric keys
+            } else {
+                $this->_queryParams = $params + $this->_queryParams;
+            }
+
+            return [$route, $this->getQueryParams()];
+        }
+
+        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
+    }
+
+    /**
+     * Filters headers according to the [[trustedHosts]].
+     * @param HeaderCollection $headerCollection
+     * @since 2.0.13
+     */
+    protected function filterHeaders(HeaderCollection $headerCollection)
+    {
+        $trustedHeaders = $this->getTrustedHeaders();
+
+        // remove all secure headers unless they are trusted
+        foreach ($this->secureHeaders as $secureHeader) {
+            if (!in_array($secureHeader, $trustedHeaders)) {
+                $headerCollection->remove($secureHeader);
+            }
+        }
+    }
+
+    /**
+     * Trusted headers according to the [[trustedHosts]].
+     * @return array
+     * @since 2.0.28
+     */
+    protected function getTrustedHeaders()
+    {
+        // do not trust any of the [[secureHeaders]] by default
+        $trustedHeaders = [];
+
+        // check if the client is a trusted host
+        if (!empty($this->trustedHosts)) {
+            $validator = $this->getIpValidator();
+            $ip = $this->getRemoteIP();
+            foreach ($this->trustedHosts as $cidr => $headers) {
+                if (!is_array($headers)) {
+                    $cidr = $headers;
+                    $headers = $this->secureHeaders;
+                }
+                $validator->setRanges($cidr);
+                if ($validator->validate($ip)) {
+                    $trustedHeaders = $headers;
+                    break;
+                }
+            }
+        }
+        return $trustedHeaders;
+    }
+
+    /**
+     * Creates instance of [[IpValidator]].
+     * You can override this method to adjust validator or implement different matching strategy.
+     *
+     * @return IpValidator
+     * @since 2.0.13
+     */
+    protected function getIpValidator()
+    {
+        return new IpValidator();
+    }
+
+    /**
+     * Returns the header collection.
+     * The header collection contains incoming HTTP headers.
+     * @return HeaderCollection the header collection
+     */
+    public function getHeaders()
+    {
+        if ($this->_headers === null) {
+            $this->_headers = new HeaderCollection();
+            if (function_exists('getallheaders')) {
+                $headers = getallheaders();
+                foreach ($headers as $name => $value) {
+                    $this->_headers->add($name, $value);
+                }
+            } elseif (function_exists('http_get_request_headers')) {
+                $headers = http_get_request_headers();
+                foreach ($headers as $name => $value) {
+                    $this->_headers->add($name, $value);
+                }
+            } else {
+                // ['prefix' => length]
+                $headerPrefixes = ['HTTP_' => 5, 'REDIRECT_HTTP_' => 14];
+
+                foreach ($_SERVER as $name => $value) {
+                    foreach ($headerPrefixes as $prefix => $length) {
+                        if (strncmp($name, $prefix, $length) === 0) {
+                            $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, $length)))));
+                            $this->_headers->add($name, $value);
+                            continue 2;
+                        }
+                    }
+                }
+            }
+            $this->filterHeaders($this->_headers);
+        }
+
+        return $this->_headers;
+    }
+
+    /**
+     * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
+     * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
+     * The value returned is turned into upper case.
+     */
+    public function getMethod()
+    {
+        if (
+            isset($_POST[$this->methodParam])
+            // Never allow to downgrade request from WRITE methods (POST, PATCH, DELETE, etc)
+            // to read methods (GET, HEAD, OPTIONS) for security reasons.
+            && !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
+        ) {
+            return strtoupper($_POST[$this->methodParam]);
+        }
+
+        if ($this->headers->has('X-Http-Method-Override')) {
+            return strtoupper($this->headers->get('X-Http-Method-Override'));
+        }
+
+        if (isset($_SERVER['REQUEST_METHOD'])) {
+            return strtoupper($_SERVER['REQUEST_METHOD']);
+        }
+
+        return 'GET';
+    }
+
+    /**
+     * Returns whether this is a GET request.
+     * @return bool whether this is a GET request.
+     */
+    public function getIsGet()
+    {
+        return $this->getMethod() === 'GET';
+    }
+
+    /**
+     * Returns whether this is an OPTIONS request.
+     * @return bool whether this is a OPTIONS request.
+     */
+    public function getIsOptions()
+    {
+        return $this->getMethod() === 'OPTIONS';
+    }
+
+    /**
+     * Returns whether this is a HEAD request.
+     * @return bool whether this is a HEAD request.
+     */
+    public function getIsHead()
+    {
+        return $this->getMethod() === 'HEAD';
+    }
+
+    /**
+     * Returns whether this is a POST request.
+     * @return bool whether this is a POST request.
+     */
+    public function getIsPost()
+    {
+        return $this->getMethod() === 'POST';
+    }
+
+    /**
+     * Returns whether this is a DELETE request.
+     * @return bool whether this is a DELETE request.
+     */
+    public function getIsDelete()
+    {
+        return $this->getMethod() === 'DELETE';
+    }
+
+    /**
+     * Returns whether this is a PUT request.
+     * @return bool whether this is a PUT request.
+     */
+    public function getIsPut()
+    {
+        return $this->getMethod() === 'PUT';
+    }
+
+    /**
+     * Returns whether this is a PATCH request.
+     * @return bool whether this is a PATCH request.
+     */
+    public function getIsPatch()
+    {
+        return $this->getMethod() === 'PATCH';
+    }
+
+    /**
+     * Returns whether this is an AJAX (XMLHttpRequest) request.
+     *
+     * Note that in case of cross domain requests, browser doesn't set the X-Requested-With header by default:
+     * https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
+     *
+     * In case you are using `fetch()`, pass header manually:
+     *
+     * ```
+     * fetch(url, {
+     *    method: 'GET',
+     *    headers: {'X-Requested-With': 'XMLHttpRequest'}
+     * })
+     * ```
+     *
+     * @return bool whether this is an AJAX (XMLHttpRequest) request.
+     */
+    public function getIsAjax()
+    {
+        return $this->headers->get('X-Requested-With') === 'XMLHttpRequest';
+    }
+
+    /**
+     * Returns whether this is a PJAX request.
+     * @return bool whether this is a PJAX request
+     */
+    public function getIsPjax()
+    {
+        return $this->getIsAjax() && $this->headers->has('X-Pjax');
+    }
+
+    /**
+     * Returns whether this is an Adobe Flash or Flex request.
+     * @return bool whether this is an Adobe Flash or Adobe Flex request.
+     */
+    public function getIsFlash()
+    {
+        $userAgent = $this->headers->get('User-Agent', '');
+        return stripos($userAgent, 'Shockwave') !== false
+            || stripos($userAgent, 'Flash') !== false;
+    }
+
+    private $_rawBody;
+
+    /**
+     * Returns the raw HTTP request body.
+     * @return string the request body
+     */
+    public function getRawBody()
+    {
+        if ($this->_rawBody === null) {
+            $this->_rawBody = file_get_contents('php://input');
+        }
+
+        return $this->_rawBody;
+    }
+
+    /**
+     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
+     * @param string $rawBody the request body
+     */
+    public function setRawBody($rawBody)
+    {
+        $this->_rawBody = $rawBody;
+    }
+
+    private $_bodyParams;
+
+    /**
+     * Returns the request parameters given in the request body.
+     *
+     * Request parameters are determined using the parsers configured in [[parsers]] property.
+     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
+     * to parse the [[rawBody|request body]].
+     * @return array the request parameters given in the request body.
+     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
+     * @see getMethod()
+     * @see getBodyParam()
+     * @see setBodyParams()
+     */
+    public function getBodyParams()
+    {
+        if ($this->_bodyParams === null) {
+            if (isset($_POST[$this->methodParam])) {
+                $this->_bodyParams = $_POST;
+                unset($this->_bodyParams[$this->methodParam]);
+                return $this->_bodyParams;
+            }
+
+            $rawContentType = $this->getContentType();
+            if (($pos = strpos($rawContentType, ';')) !== false) {
+                // e.g. text/html; charset=UTF-8
+                $contentType = substr($rawContentType, 0, $pos);
+            } else {
+                $contentType = $rawContentType;
+            }
+
+            if (isset($this->parsers[$contentType])) {
+                $parser = Yii::createObject($this->parsers[$contentType]);
+                if (!($parser instanceof RequestParserInterface)) {
+                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
+                }
+                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
+            } elseif (isset($this->parsers['*'])) {
+                $parser = Yii::createObject($this->parsers['*']);
+                if (!($parser instanceof RequestParserInterface)) {
+                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
+                }
+                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
+            } elseif ($this->getMethod() === 'POST') {
+                // PHP has already parsed the body so we have all params in $_POST
+                $this->_bodyParams = $_POST;
+            } else {
+                $this->_bodyParams = [];
+                mb_parse_str($this->getRawBody(), $this->_bodyParams);
+            }
+        }
+
+        return $this->_bodyParams;
+    }
+
+    /**
+     * Sets the request body parameters.
+     * @param array $values the request body parameters (name-value pairs)
+     * @see getBodyParam()
+     * @see getBodyParams()
+     */
+    public function setBodyParams($values)
+    {
+        $this->_bodyParams = $values;
+    }
+
+    /**
+     * Returns the named request body parameter value.
+     * If the parameter does not exist, the second parameter passed to this method will be returned.
+     * @param string $name the parameter name
+     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
+     * @return mixed the parameter value
+     * @see getBodyParams()
+     * @see setBodyParams()
+     */
+    public function getBodyParam($name, $defaultValue = null)
+    {
+        $params = $this->getBodyParams();
+
+        if (is_object($params)) {
+            // unable to use `ArrayHelper::getValue()` due to different dots in key logic and lack of exception handling
+            try {
+                return $params->{$name};
+            } catch (\Exception $e) {
+                return $defaultValue;
+            }
+        }
+
+        return isset($params[$name]) ? $params[$name] : $defaultValue;
+    }
+
+    /**
+     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
+     *
+     * @param string $name the parameter name
+     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
+     * @return array|mixed
+     */
+    public function post($name = null, $defaultValue = null)
+    {
+        if ($name === null) {
+            return $this->getBodyParams();
+        }
+
+        return $this->getBodyParam($name, $defaultValue);
+    }
+
+    private $_queryParams;
+
+    /**
+     * Returns the request parameters given in the [[queryString]].
+     *
+     * This method will return the contents of `$_GET` if params where not explicitly set.
+     * @return array the request GET parameter values.
+     * @see setQueryParams()
+     */
+    public function getQueryParams()
+    {
+        if ($this->_queryParams === null) {
+            return $_GET;
+        }
+
+        return $this->_queryParams;
+    }
+
+    /**
+     * Sets the request [[queryString]] parameters.
+     * @param array $values the request query parameters (name-value pairs)
+     * @see getQueryParam()
+     * @see getQueryParams()
+     */
+    public function setQueryParams($values)
+    {
+        $this->_queryParams = $values;
+    }
+
+    /**
+     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
+     *
+     * @param string $name the parameter name
+     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
+     * @return array|mixed
+     */
+    public function get($name = null, $defaultValue = null)
+    {
+        if ($name === null) {
+            return $this->getQueryParams();
+        }
+
+        return $this->getQueryParam($name, $defaultValue);
+    }
+
+    /**
+     * Returns the named GET parameter value.
+     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
+     * @param string $name the GET parameter name.
+     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
+     * @return mixed the GET parameter value
+     * @see getBodyParam()
+     */
+    public function getQueryParam($name, $defaultValue = null)
+    {
+        $params = $this->getQueryParams();
+
+        return isset($params[$name]) ? $params[$name] : $defaultValue;
+    }
+
+    private $_hostInfo;
+    private $_hostName;
+
+    /**
+     * Returns the schema and host part of the current request URL.
+     *
+     * The returned URL does not have an ending slash.
+     *
+     * By default this value is based on the user request information. This method will
+     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
+     * You may want to check out the [PHP documentation](https://secure.php.net/manual/en/reserved.variables.server.php)
+     * for more information on these variables.
+     *
+     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
+     *
+     * > Warning: Dependent on the server configuration this information may not be
+     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
+     * > If the webserver is configured to serve the same site independent of the value of
+     * > the `Host` header, this value is not reliable. In such situations you should either
+     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
+     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
+     * > application level in order to protect against such kind of attack.
+     *
+     * @property string|null schema and hostname part (with port number if needed) of the request URL
+     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
+     * See [[getHostInfo()]] for security related notes on this property.
+     * @return string|null schema and hostname part (with port number if needed) of the request URL
+     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
+     * @see setHostInfo()
+     */
+    public function getHostInfo()
+    {
+        if ($this->_hostInfo === null) {
+            $secure = $this->getIsSecureConnection();
+            $http = $secure ? 'https' : 'http';
+
+            if ($this->getSecureForwardedHeaderTrustedPart('host') !== null) {
+                $this->_hostInfo = $http . '://' . $this->getSecureForwardedHeaderTrustedPart('host');
+            } elseif ($this->headers->has('X-Forwarded-Host')) {
+                $this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Forwarded-Host'))[0]);
+            } elseif ($this->headers->has('X-Original-Host')) {
+                $this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Original-Host'))[0]);
+            } elseif ($this->headers->has('Host')) {
+                $this->_hostInfo = $http . '://' . $this->headers->get('Host');
+            } elseif (isset($_SERVER['SERVER_NAME'])) {
+                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
+                $port = $secure ? $this->getSecurePort() : $this->getPort();
+                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
+                    $this->_hostInfo .= ':' . $port;
+                }
+            }
+        }
+
+        return $this->_hostInfo;
+    }
+
+    /**
+     * Sets the schema and host part of the application URL.
+     * This setter is provided in case the schema and hostname cannot be determined
+     * on certain Web servers.
+     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
+     * @see getHostInfo() for security related notes on this property.
+     */
+    public function setHostInfo($value)
+    {
+        $this->_hostName = null;
+        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
+    }
+
+    /**
+     * Returns the host part of the current request URL.
+     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
+     *
+     * > Warning: The content of this value may not be reliable, dependent on the server
+     * > configuration. Please refer to [[getHostInfo()]] for more information.
+     *
+     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
+     * @see getHostInfo()
+     * @since 2.0.10
+     */
+    public function getHostName()
+    {
+        if ($this->_hostName === null) {
+            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
+        }
+
+        return $this->_hostName;
+    }
+
+    private $_baseUrl;
+
+    /**
+     * Returns the relative URL for the application.
+     * This is similar to [[scriptUrl]] except that it does not include the script file name,
+     * and the ending slashes are removed.
+     * @return string the relative URL for the application
+     * @see setScriptUrl()
+     */
+    public function getBaseUrl()
+    {
+        if ($this->_baseUrl === null) {
+            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
+        }
+
+        return $this->_baseUrl;
+    }
+
+    /**
+     * Sets the relative URL for the application.
+     * By default the URL is determined based on the entry script URL.
+     * This setter is provided in case you want to change this behavior.
+     * @param string $value the relative URL for the application
+     */
+    public function setBaseUrl($value)
+    {
+        $this->_baseUrl = $value;
+    }
+
+    private $_scriptUrl;
+
+    /**
+     * Returns the relative URL of the entry script.
+     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
+     * @return string the relative URL of the entry script.
+     * @throws InvalidConfigException if unable to determine the entry script URL
+     */
+    public function getScriptUrl()
+    {
+        if ($this->_scriptUrl === null) {
+            $scriptFile = $this->getScriptFile();
+            $scriptName = basename($scriptFile);
+            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
+                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
+            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
+                $this->_scriptUrl = $_SERVER['PHP_SELF'];
+            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
+                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
+            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
+                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
+            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
+                $this->_scriptUrl = str_replace([$_SERVER['DOCUMENT_ROOT'], '\\'], ['', '/'], $scriptFile);
+            } else {
+                throw new InvalidConfigException('Unable to determine the entry script URL.');
+            }
+        }
+
+        return $this->_scriptUrl;
+    }
+
+    /**
+     * Sets the relative URL for the application entry script.
+     * This setter is provided in case the entry script URL cannot be determined
+     * on certain Web servers.
+     * @param string $value the relative URL for the application entry script.
+     */
+    public function setScriptUrl($value)
+    {
+        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
+    }
+
+    private $_scriptFile;
+
+    /**
+     * Returns the entry script file path.
+     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
+     * @return string the entry script file path
+     * @throws InvalidConfigException
+     */
+    public function getScriptFile()
+    {
+        if (isset($this->_scriptFile)) {
+            return $this->_scriptFile;
+        }
+
+        if (isset($_SERVER['SCRIPT_FILENAME'])) {
+            return $_SERVER['SCRIPT_FILENAME'];
+        }
+
+        throw new InvalidConfigException('Unable to determine the entry script file path.');
+    }
+
+    /**
+     * Sets the entry script file path.
+     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
+     * If your server configuration does not return the correct value, you may configure
+     * this property to make it right.
+     * @param string $value the entry script file path.
+     */
+    public function setScriptFile($value)
+    {
+        $this->_scriptFile = $value;
+    }
+
+    private $_pathInfo;
+
+    /**
+     * Returns the path info of the currently requested URL.
+     * A path info refers to the part that is after the entry script and before the question mark (query string).
+     * The starting and ending slashes are both removed.
+     * @return string part of the request URL that is after the entry script and before the question mark.
+     * Note, the returned path info is already URL-decoded.
+     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
+     */
+    public function getPathInfo()
+    {
+        if ($this->_pathInfo === null) {
+            $this->_pathInfo = $this->resolvePathInfo();
+        }
+
+        return $this->_pathInfo;
+    }
+
+    /**
+     * Sets the path info of the current request.
+     * This method is mainly provided for testing purpose.
+     * @param string $value the path info of the current request
+     */
+    public function setPathInfo($value)
+    {
+        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
+    }
+
+    /**
+     * Resolves the path info part of the currently requested URL.
+     * A path info refers to the part that is after the entry script and before the question mark (query string).
+     * The starting slashes are both removed (ending slashes will be kept).
+     * @return string part of the request URL that is after the entry script and before the question mark.
+     * Note, the returned path info is decoded.
+     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
+     */
+    protected function resolvePathInfo()
+    {
+        $pathInfo = $this->getUrl();
+
+        if (($pos = strpos($pathInfo, '?')) !== false) {
+            $pathInfo = substr($pathInfo, 0, $pos);
+        }
+
+        $pathInfo = urldecode($pathInfo);
+
+        // try to encode in UTF8 if not so
+        // http://w3.org/International/questions/qa-forms-utf-8.html
+        if (!preg_match('%^(?:
+            [\x09\x0A\x0D\x20-\x7E]              # ASCII
+            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
+            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
+            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
+            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
+            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
+            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
+            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
+            )*$%xs', $pathInfo)
+        ) {
+            $pathInfo = $this->utf8Encode($pathInfo);
+        }
+
+        $scriptUrl = $this->getScriptUrl();
+        $baseUrl = $this->getBaseUrl();
+        if (strpos($pathInfo, $scriptUrl) === 0) {
+            $pathInfo = substr($pathInfo, strlen($scriptUrl));
+        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
+            $pathInfo = substr($pathInfo, strlen($baseUrl));
+        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
+            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
+        } else {
+            throw new InvalidConfigException('Unable to determine the path info of the current request.');
+        }
+
+        if (strncmp($pathInfo, '/', 1) === 0) {
+            $pathInfo = substr($pathInfo, 1);
+        }
+
+        return (string) $pathInfo;
+    }
+
+    /**
+     * Encodes an ISO-8859-1 string to UTF-8
+     * @param string $s
+     * @return string the UTF-8 translation of `s`.
+     * @see https://github.com/symfony/polyfill-php72/blob/master/Php72.php#L24
+     */
+    private function utf8Encode($s)
+    {
+        $s .= $s;
+        $len = \strlen($s);
+        for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
+            switch (true) {
+                case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
+                case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
+                default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
+            }
+        }
+        return substr($s, 0, $j);
+    }
+
+    /**
+     * Returns the currently requested absolute URL.
+     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
+     * @return string the currently requested absolute URL.
+     */
+    public function getAbsoluteUrl()
+    {
+        return $this->getHostInfo() . $this->getUrl();
+    }
+
+    private $_url;
+
+    /**
+     * Returns the currently requested relative URL.
+     * This refers to the portion of the URL that is after the [[hostInfo]] part.
+     * It includes the [[queryString]] part if any.
+     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
+     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
+     */
+    public function getUrl()
+    {
+        if ($this->_url === null) {
+            $this->_url = $this->resolveRequestUri();
+        }
+
+        return $this->_url;
+    }
+
+    /**
+     * Sets the currently requested relative URL.
+     * The URI must refer to the portion that is after [[hostInfo]].
+     * Note that the URI should be URL-encoded.
+     * @param string $value the request URI to be set
+     */
+    public function setUrl($value)
+    {
+        $this->_url = $value;
+    }
+
+    /**
+     * Resolves the request URI portion for the currently requested URL.
+     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
+     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
+     * @return string|bool the request URI portion for the currently requested URL.
+     * Note that the URI returned may be URL-encoded depending on the client.
+     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
+     */
+    protected function resolveRequestUri()
+    {
+        if ($this->headers->has('X-Rewrite-Url')) { // IIS
+            $requestUri = $this->headers->get('X-Rewrite-Url');
+        } elseif (isset($_SERVER['REQUEST_URI'])) {
+            $requestUri = $_SERVER['REQUEST_URI'];
+            if ($requestUri !== '' && $requestUri[0] !== '/') {
+                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
+            }
+        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
+            $requestUri = $_SERVER['ORIG_PATH_INFO'];
+            if (!empty($_SERVER['QUERY_STRING'])) {
+                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
+            }
+        } else {
+            throw new InvalidConfigException('Unable to determine the request URI.');
+        }
+
+        return $requestUri;
+    }
+
+    /**
+     * Returns part of the request URL that is after the question mark.
+     * @return string part of the request URL that is after the question mark
+     */
+    public function getQueryString()
+    {
+        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
+    }
+
+    /**
+     * Return if the request is sent via secure channel (https).
+     * @return bool if the request is sent via secure channel (https)
+     */
+    public function getIsSecureConnection()
+    {
+        if (isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)) {
+            return true;
+        }
+
+        if (($proto = $this->getSecureForwardedHeaderTrustedPart('proto')) !== null) {
+            return strcasecmp($proto, 'https') === 0;
+        }
+
+        foreach ($this->secureProtocolHeaders as $header => $values) {
+            if (($headerValue = $this->headers->get($header, null)) !== null) {
+                foreach ($values as $value) {
+                    if (strcasecmp($headerValue, $value) === 0) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the server name.
+     * @return string server name, null if not available
+     */
+    public function getServerName()
+    {
+        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
+    }
+
+    /**
+     * Returns the server port number.
+     * @return int|null server port number, null if not available
+     */
+    public function getServerPort()
+    {
+        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
+    }
+
+    /**
+     * Returns the URL referrer.
+     * @return string|null URL referrer, null if not available
+     */
+    public function getReferrer()
+    {
+        return $this->headers->get('Referer');
+    }
+
+    /**
+     * Returns the URL origin of a CORS request.
+     *
+     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
+     *
+     * Note that the origin request header indicates where a fetch originates from.
+     * It doesn't include any path information, but only the server name.
+     * It is sent with a CORS requests, as well as with POST requests.
+     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
+     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
+     *
+     * @return string|null URL origin of a CORS request, `null` if not available.
+     * @see getHeaders()
+     * @since 2.0.13
+     */
+    public function getOrigin()
+    {
+        return $this->getHeaders()->get('origin');
+    }
+
+    /**
+     * Returns the user agent.
+     * @return string|null user agent, null if not available
+     */
+    public function getUserAgent()
+    {
+        return $this->headers->get('User-Agent');
+    }
+
+    /**
+     * Returns the user IP address from [[ipHeaders]].
+     * @return string|null user IP address, null if not available
+     * @see $ipHeaders
+     * @since 2.0.28
+     */
+    protected function getUserIpFromIpHeaders()
+    {
+        $ip = $this->getSecureForwardedHeaderTrustedPart('for');
+        if ($ip !== null && preg_match(
+            '/^\[?(?P<ip>(?:(?:(?:[0-9a-f]{1,4}:){1,6}(?:[0-9a-f]{1,4})?(?:(?::[0-9a-f]{1,4}){1,6}))|(?:[\d]{1,3}\.){3}[\d]{1,3}))\]?(?::(?P<port>[\d]+))?$/',
+            $ip,
+            $matches
+        )) {
+            $ip = $this->getUserIpFromIpHeader($matches['ip']);
+            if ($ip !== null) {
+                return $ip;
+            }
+        }
+
+
+        foreach ($this->ipHeaders as $ipHeader) {
+            if ($this->headers->has($ipHeader)) {
+                $ip = $this->getUserIpFromIpHeader($this->headers->get($ipHeader));
+                if ($ip !== null) {
+                    return $ip;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the user IP address.
+     * The IP is determined using headers and / or `$_SERVER` variables.
+     * @return string|null user IP address, null if not available
+     */
+    public function getUserIP()
+    {
+        $ip = $this->getUserIpFromIpHeaders();
+        return $ip === null ? $this->getRemoteIP() : $ip;
+    }
+
+    /**
+     * Return user IP's from IP header.
+     *
+     * @param string $ips comma separated IP list
+     * @return string|null IP as string. Null is returned if IP can not be determined from header.
+     * @see $getUserHost
+     * @see $ipHeader
+     * @see $trustedHeaders
+     * @since 2.0.28
+     */
+    protected function getUserIpFromIpHeader($ips)
+    {
+        $ips = trim($ips);
+        if ($ips === '') {
+            return null;
+        }
+        $ips = preg_split('/\s*,\s*/', $ips, -1, PREG_SPLIT_NO_EMPTY);
+        krsort($ips);
+        $validator = $this->getIpValidator();
+        $resultIp = null;
+        foreach ($ips as $ip) {
+            $validator->setRanges('any');
+            if (!$validator->validate($ip) /* checking IP format */) {
+                break;
+            }
+            $resultIp = $ip;
+            $isTrusted = false;
+            foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
+                if (!is_array($trustedCidrOrHeaders)) {
+                    $trustedCidr = $trustedCidrOrHeaders;
+                }
+                $validator->setRanges($trustedCidr);
+                if ($validator->validate($ip) /* checking trusted range */) {
+                    $isTrusted = true;
+                    break;
+                }
+            }
+            if (!$isTrusted) {
+                break;
+            }
+        }
+        return $resultIp;
+    }
+
+    /**
+     * Returns the user host name.
+     * The HOST is determined using headers and / or `$_SERVER` variables.
+     * @return string|null user host name, null if not available
+     */
+    public function getUserHost()
+    {
+        $userIp = $this->getUserIpFromIpHeaders();
+        if($userIp === null) {
+            return $this->getRemoteHost();
+        }
+        return gethostbyaddr($userIp);
+    }
+
+    /**
+     * Returns the IP on the other end of this connection.
+     * This is always the next hop, any headers are ignored.
+     * @return string|null remote IP address, `null` if not available.
+     * @since 2.0.13
+     */
+    public function getRemoteIP()
+    {
+        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
+    }
+
+    /**
+     * Returns the host name of the other end of this connection.
+     * This is always the next hop, any headers are ignored.
+     * @return string|null remote host name, `null` if not available
+     * @see getUserHost()
+     * @see getRemoteIP()
+     * @since 2.0.13
+     */
+    public function getRemoteHost()
+    {
+        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
+    }
+
+    /**
+     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
+     * @see getAuthCredentials() to get both username and password in one call
+     */
+    public function getAuthUser()
+    {
+        return $this->getAuthCredentials()[0];
+    }
+
+    /**
+     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
+     * @see getAuthCredentials() to get both username and password in one call
+     */
+    public function getAuthPassword()
+    {
+        return $this->getAuthCredentials()[1];
+    }
+
+    /**
+     * @return array that contains exactly two elements:
+     * - 0: the username sent via HTTP authentication, `null` if the username is not given
+     * - 1: the password sent via HTTP authentication, `null` if the password is not given
+     * @see getAuthUser() to get only username
+     * @see getAuthPassword() to get only password
+     * @since 2.0.13
+     */
+    public function getAuthCredentials()
+    {
+        $username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
+        $password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
+        if ($username !== null || $password !== null) {
+            return [$username, $password];
+        }
+
+        /**
+         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
+         * To make it work, add one of the following lines to to your .htaccess file:
+         *
+         * SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
+         * --OR--
+         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+         */
+        $auth_token = $this->getHeaders()->get('Authorization');
+
+        if ($auth_token !== null && strncasecmp($auth_token, 'basic', 5) === 0) {
+            $parts = array_map(function ($value) {
+                return strlen($value) === 0 ? null : $value;
+            }, explode(':', base64_decode(mb_substr($auth_token, 6)), 2));
+
+            if (count($parts) < 2) {
+                return [$parts[0], null];
+            }
+
+            return $parts;
+        }
+
+        return [null, null];
+    }
+
+    private $_port;
+
+    /**
+     * Returns the port to use for insecure requests.
+     * Defaults to 80, or the port specified by the server if the current
+     * request is insecure.
+     * @return int port number for insecure requests.
+     * @see setPort()
+     */
+    public function getPort()
+    {
+        if ($this->_port === null) {
+            $serverPort = $this->getServerPort();
+            $this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
+        }
+
+        return $this->_port;
+    }
+
+    /**
+     * Sets the port to use for insecure requests.
+     * This setter is provided in case a custom port is necessary for certain
+     * server configurations.
+     * @param int $value port number.
+     */
+    public function setPort($value)
+    {
+        if ($value != $this->_port) {
+            $this->_port = (int) $value;
+            $this->_hostInfo = null;
+        }
+    }
+
+    private $_securePort;
+
+    /**
+     * Returns the port to use for secure requests.
+     * Defaults to 443, or the port specified by the server if the current
+     * request is secure.
+     * @return int port number for secure requests.
+     * @see setSecurePort()
+     */
+    public function getSecurePort()
+    {
+        if ($this->_securePort === null) {
+            $serverPort = $this->getServerPort();
+            $this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
+        }
+
+        return $this->_securePort;
+    }
+
+    /**
+     * Sets the port to use for secure requests.
+     * This setter is provided in case a custom port is necessary for certain
+     * server configurations.
+     * @param int $value port number.
+     */
+    public function setSecurePort($value)
+    {
+        if ($value != $this->_securePort) {
+            $this->_securePort = (int) $value;
+            $this->_hostInfo = null;
+        }
+    }
+
+    private $_contentTypes;
+
+    /**
+     * Returns the content types acceptable by the end user.
+     *
+     * This is determined by the `Accept` HTTP header. For example,
+     *
+     * ```php
+     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
+     * $types = $request->getAcceptableContentTypes();
+     * print_r($types);
+     * // displays:
+     * // [
+     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
+     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
+     * //           'text/plain' => ['q' => 0.5],
+     * // ]
+     * ```
+     *
+     * @return array the content types ordered by the quality score. Types with the highest scores
+     * will be returned first. The array keys are the content types, while the array values
+     * are the corresponding quality score and other parameters as given in the header.
+     */
+    public function getAcceptableContentTypes()
+    {
+        if ($this->_contentTypes === null) {
+            if ($this->headers->get('Accept') !== null) {
+                $this->_contentTypes = $this->parseAcceptHeader($this->headers->get('Accept'));
+            } else {
+                $this->_contentTypes = [];
+            }
+        }
+
+        return $this->_contentTypes;
+    }
+
+    /**
+     * Sets the acceptable content types.
+     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
+     * @param array $value the content types that are acceptable by the end user. They should
+     * be ordered by the preference level.
+     * @see getAcceptableContentTypes()
+     * @see parseAcceptHeader()
+     */
+    public function setAcceptableContentTypes($value)
+    {
+        $this->_contentTypes = $value;
+    }
+
+    /**
+     * Returns request content-type
+     * The Content-Type header field indicates the MIME type of the data
+     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
+     * media type that would have been sent had the request been a GET.
+     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
+     * @return string request content-type. Null is returned if this information is not available.
+     * @link https://tools.ietf.org/html/rfc2616#section-14.17
+     * HTTP 1.1 header field definitions
+     */
+    public function getContentType()
+    {
+        if (isset($_SERVER['CONTENT_TYPE'])) {
+            return $_SERVER['CONTENT_TYPE'];
+        }
+
+        //fix bug https://bugs.php.net/bug.php?id=66606
+        return $this->headers->get('Content-Type');
+    }
+
+    private $_languages;
+
+    /**
+     * Returns the languages acceptable by the end user.
+     * This is determined by the `Accept-Language` HTTP header.
+     * @return array the languages ordered by the preference level. The first element
+     * represents the most preferred language.
+     */
+    public function getAcceptableLanguages()
+    {
+        if ($this->_languages === null) {
+            if ($this->headers->has('Accept-Language')) {
+                $this->_languages = array_keys($this->parseAcceptHeader($this->headers->get('Accept-Language')));
+            } else {
+                $this->_languages = [];
+            }
+        }
+
+        return $this->_languages;
+    }
+
+    /**
+     * @param array $value the languages that are acceptable by the end user. They should
+     * be ordered by the preference level.
+     */
+    public function setAcceptableLanguages($value)
+    {
+        $this->_languages = $value;
+    }
+
+    /**
+     * Parses the given `Accept` (or `Accept-Language`) header.
+     *
+     * This method will return the acceptable values with their quality scores and the corresponding parameters
+     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
+     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
+     * values with the highest quality scores will be returned first. For example,
+     *
+     * ```php
+     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
+     * $accepts = $request->parseAcceptHeader($header);
+     * print_r($accepts);
+     * // displays:
+     * // [
+     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
+     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
+     * //           'text/plain' => ['q' => 0.5],
+     * // ]
+     * ```
+     *
+     * @param string $header the header to be parsed
+     * @return array the acceptable values ordered by their quality score. The values with the highest scores
+     * will be returned first.
+     */
+    public function parseAcceptHeader($header)
+    {
+        $accepts = [];
+        foreach (explode(',', $header) as $i => $part) {
+            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
+            if (empty($params)) {
+                continue;
+            }
+            $values = [
+                'q' => [$i, array_shift($params), 1],
+            ];
+            foreach ($params as $param) {
+                if (strpos($param, '=') !== false) {
+                    list($key, $value) = explode('=', $param, 2);
+                    if ($key === 'q') {
+                        $values['q'][2] = (float) $value;
+                    } else {
+                        $values[$key] = $value;
+                    }
+                } else {
+                    $values[] = $param;
+                }
+            }
+            $accepts[] = $values;
+        }
+
+        usort($accepts, function ($a, $b) {
+            $a = $a['q']; // index, name, q
+            $b = $b['q'];
+            if ($a[2] > $b[2]) {
+                return -1;
+            }
+
+            if ($a[2] < $b[2]) {
+                return 1;
+            }
+
+            if ($a[1] === $b[1]) {
+                return $a[0] > $b[0] ? 1 : -1;
+            }
+
+            if ($a[1] === '*/*') {
+                return 1;
+            }
+
+            if ($b[1] === '*/*') {
+                return -1;
+            }
+
+            $wa = $a[1][strlen($a[1]) - 1] === '*';
+            $wb = $b[1][strlen($b[1]) - 1] === '*';
+            if ($wa xor $wb) {
+                return $wa ? 1 : -1;
+            }
+
+            return $a[0] > $b[0] ? 1 : -1;
+        });
+
+        $result = [];
+        foreach ($accepts as $accept) {
+            $name = $accept['q'][1];
+            $accept['q'] = $accept['q'][2];
+            $result[$name] = $accept;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns the user-preferred language that should be used by this application.
+     * The language resolution is based on the user preferred languages and the languages
+     * supported by the application. The method will try to find the best match.
+     * @param array $languages a list of the languages supported by the application. If this is empty, the current
+     * application language will be returned without further processing.
+     * @return string the language that the application should use.
+     */
+    public function getPreferredLanguage(array $languages = [])
+    {
+        if (empty($languages)) {
+            return Yii::$app->language;
+        }
+        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
+            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
+            foreach ($languages as $language) {
+                $normalizedLanguage = str_replace('_', '-', strtolower($language));
+
+                if (
+                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
+                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
+                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
+                ) {
+                    return $language;
+                }
+            }
+        }
+
+        return reset($languages);
+    }
+
+    /**
+     * Gets the Etags.
+     *
+     * @return array The entity tags
+     */
+    public function getETags()
+    {
+        if ($this->headers->has('If-None-Match')) {
+            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->headers->get('If-None-Match')), -1, PREG_SPLIT_NO_EMPTY);
+        }
+
+        return [];
+    }
+
+    /**
+     * Returns the cookie collection.
+     *
+     * Through the returned cookie collection, you may access a cookie using the following syntax:
+     *
+     * ```php
+     * $cookie = $request->cookies['name']
+     * if ($cookie !== null) {
+     *     $value = $cookie->value;
+     * }
+     *
+     * // alternatively
+     * $value = $request->cookies->getValue('name');
+     * ```
+     *
+     * @return CookieCollection the cookie collection.
+     */
+    public function getCookies()
+    {
+        if ($this->_cookies === null) {
+            $this->_cookies = new CookieCollection($this->loadCookies(), [
+                'readOnly' => true,
+            ]);
+        }
+
+        return $this->_cookies;
+    }
+
+    /**
+     * Converts `$_COOKIE` into an array of [[Cookie]].
+     * @return array the cookies obtained from request
+     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
+     */
+    protected function loadCookies()
+    {
+        $cookies = [];
+        if ($this->enableCookieValidation) {
+            if ($this->cookieValidationKey == '') {
+                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
+            }
+            foreach ($_COOKIE as $name => $value) {
+                if (!is_string($value)) {
+                    continue;
+                }
+                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
+                if ($data === false) {
+                    continue;
+                }
+                if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000) {
+                    $data = @unserialize($data, ['allowed_classes' => false]);
+                } else {
+                    $data = @unserialize($data);
+                }
+                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
+                    $cookies[$name] = Yii::createObject([
+                        'class' => 'yii\web\Cookie',
+                        'name' => $name,
+                        'value' => $data[1],
+                        'expire' => null,
+                    ]);
+                }
+            }
+        } else {
+            foreach ($_COOKIE as $name => $value) {
+                $cookies[$name] = Yii::createObject([
+                    'class' => 'yii\web\Cookie',
+                    'name' => $name,
+                    'value' => $value,
+                    'expire' => null,
+                ]);
+            }
+        }
+
+        return $cookies;
+    }
+
+    private $_csrfToken;
+
+    /**
+     * Returns the token used to perform CSRF validation.
+     *
+     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
+     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
+     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
+     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
+     * @return string the token used to perform CSRF validation.
+     */
+    public function getCsrfToken($regenerate = false)
+    {
+        if ($this->_csrfToken === null || $regenerate) {
+            $token = $this->loadCsrfToken();
+            if ($regenerate || empty($token)) {
+                $token = $this->generateCsrfToken();
+            }
+            $this->_csrfToken = Yii::$app->security->maskToken($token);
+        }
+
+        return $this->_csrfToken;
+    }
+
+    /**
+     * Loads the CSRF token from cookie or session.
+     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
+     * does not have CSRF token.
+     */
+    protected function loadCsrfToken()
+    {
+        if ($this->enableCsrfCookie) {
+            return $this->getCookies()->getValue($this->csrfParam);
+        }
+
+        return Yii::$app->getSession()->get($this->csrfParam);
+    }
+
+    /**
+     * Generates an unmasked random token used to perform CSRF validation.
+     * @return string the random token for CSRF validation.
+     */
+    protected function generateCsrfToken()
+    {
+        $token = Yii::$app->getSecurity()->generateRandomString();
+        if ($this->enableCsrfCookie) {
+            $cookie = $this->createCsrfCookie($token);
+            Yii::$app->getResponse()->getCookies()->add($cookie);
+        } else {
+            Yii::$app->getSession()->set($this->csrfParam, $token);
+        }
+
+        return $token;
+    }
+
+    /**
+     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
+     */
+    public function getCsrfTokenFromHeader()
+    {
+        return $this->headers->get(static::CSRF_HEADER);
+    }
+
+    /**
+     * Creates a cookie with a randomly generated CSRF token.
+     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
+     * @param string $token the CSRF token
+     * @return Cookie the generated cookie
+     * @see enableCsrfValidation
+     */
+    protected function createCsrfCookie($token)
+    {
+        $options = $this->csrfCookie;
+        return Yii::createObject(array_merge($options, [
+            'class' => 'yii\web\Cookie',
+            'name' => $this->csrfParam,
+            'value' => $token,
+        ]));
+    }
+
+    /**
+     * Performs the CSRF validation.
+     *
+     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
+     * This method is mainly called in [[Controller::beforeAction()]].
+     *
+     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
+     * is among GET, HEAD or OPTIONS.
+     *
+     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
+     * the [[csrfParam]] POST field or HTTP header.
+     * This parameter is available since version 2.0.4.
+     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
+     */
+    public function validateCsrfToken($clientSuppliedToken = null)
+    {
+        $method = $this->getMethod();
+        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
+        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
+            return true;
+        }
+
+        $trueToken = $this->getCsrfToken();
+
+        if ($clientSuppliedToken !== null) {
+            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
+        }
+
+        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
+            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
+    }
+
+    /**
+     * Validates CSRF token.
+     *
+     * @param string $clientSuppliedToken The masked client-supplied token.
+     * @param string $trueToken The masked true token.
+     * @return bool
+     */
+    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
+    {
+        if (!is_string($clientSuppliedToken)) {
+            return false;
+        }
+
+        $security = Yii::$app->security;
+
+        return $security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken));
+    }
+
+    /**
+     * Gets first `Forwarded` header value for token
+     *
+     * @param string $token Header token
+     *
+     * @return string|null
+     *
+     * @since 2.0.31
+     */
+    protected function getSecureForwardedHeaderTrustedPart($token)
+    {
+        $token = strtolower($token);
+
+        if ($parts = $this->getSecureForwardedHeaderTrustedParts()) {
+            $lastElement = array_pop($parts);
+            if ($lastElement && isset($lastElement[$token])) {
+                return $lastElement[$token];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets only trusted `Forwarded` header parts
+     *
+     * @return array
+     *
+     * @since 2.0.31
+     */
+    protected function getSecureForwardedHeaderTrustedParts()
+    {
+        $validator = $this->getIpValidator();
+        $trustedHosts = [];
+        foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
+            if (!is_array($trustedCidrOrHeaders)) {
+                $trustedCidr = $trustedCidrOrHeaders;
+            }
+            $trustedHosts[] = $trustedCidr;
+        }
+        $validator->setRanges($trustedHosts);
+
+        return array_filter($this->getSecureForwardedHeaderParts(), function ($headerPart) use ($validator) {
+            return isset($headerPart['for']) ? !$validator->validate($headerPart['for']) : true;
+        });
+    }
+
+    private $_secureForwardedHeaderParts;
+
+    /**
+     * Returns decoded forwarded header
+     *
+     * @return array
+     *
+     * @since 2.0.31
+     */
+    protected function getSecureForwardedHeaderParts()
+    {
+        if ($this->_secureForwardedHeaderParts !== null) {
+            return $this->_secureForwardedHeaderParts;
+        }
+        if (count(preg_grep('/^forwarded$/i', $this->secureHeaders)) === 0) {
+            return $this->_secureForwardedHeaderParts = [];
+        }
+        /*
+         * First header is always correct, because proxy CAN add headers
+         * after last one is found.
+         * Keep in mind that it is NOT enforced, therefore we cannot be
+         * sure, that this is really a first one.
+         *
+         * FPM keeps last header sent which is a bug. You need to merge
+         * headers together on your web server before letting FPM handle it
+         * @see https://bugs.php.net/bug.php?id=78844
+         */
+        $forwarded = $this->headers->get('Forwarded', '');
+        if ($forwarded === '') {
+            return $this->_secureForwardedHeaderParts = [];
+        }
+
+        preg_match_all('/(?:[^",]++|"[^"]++")+/', $forwarded, $forwardedElements);
+
+        foreach ($forwardedElements[0] as $forwardedPairs) {
+            preg_match_all('/(?P<key>\w+)\s*=\s*(?:(?P<value>[^",;]*[^",;\s])|"(?P<value2>[^"]+)")/', $forwardedPairs,
+                $matches, PREG_SET_ORDER);
+            $this->_secureForwardedHeaderParts[] = array_reduce($matches, function ($carry, $item) {
+                $value = $item['value'];
+                if (isset($item['value2']) && $item['value2'] !== '') {
+                    $value = $item['value2'];
+                }
+                $carry[strtolower($item['key'])] = $value;
+                return $carry;
+            }, []);
+        }
+        return $this->_secureForwardedHeaderParts;
+    }
+}

+ 25 - 0
vendor/yiisoft/yii2/web/RequestParserInterface.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * Interface for classes that parse the raw request body into a parameters array.
+ *
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+interface RequestParserInterface
+{
+    /**
+     * Parses a HTTP request body.
+     * @param string $rawBody the raw HTTP request body.
+     * @param string $contentType the content type specified for the request body.
+     * @return array parameters parsed from the request body
+     */
+    public function parse($rawBody, $contentType);
+}

+ 1138 - 0
vendor/yiisoft/yii2/web/Response.php

@@ -0,0 +1,1138 @@
+<?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\InvalidArgumentException;
+use yii\base\InvalidConfigException;
+use yii\helpers\FileHelper;
+use yii\helpers\Inflector;
+use yii\helpers\StringHelper;
+use yii\helpers\Url;
+
+/**
+ * The web Response class represents an HTTP response.
+ *
+ * It holds the [[headers]], [[cookies]] and [[content]] that is to be sent to the client.
+ * It also controls the HTTP [[statusCode|status code]].
+ *
+ * Response is configured as an application component in [[\yii\web\Application]] by default.
+ * You can access that instance via `Yii::$app->response`.
+ *
+ * You can modify its configuration by adding an array to your application config under `components`
+ * as it is shown in the following example:
+ *
+ * ```php
+ * 'response' => [
+ *     'format' => yii\web\Response::FORMAT_JSON,
+ *     'charset' => 'UTF-8',
+ *     // ...
+ * ]
+ * ```
+ *
+ * For more details and usage information on Response, see the [guide article on responses](guide:runtime-responses).
+ *
+ * @property-read CookieCollection $cookies The cookie collection. This property is read-only.
+ * @property-write string $downloadHeaders The attachment file name. This property is write-only.
+ * @property-read HeaderCollection $headers The header collection. This property is read-only.
+ * @property-read bool $isClientError Whether this response indicates a client error. This property is
+ * read-only.
+ * @property-read bool $isEmpty Whether this response is empty. This property is read-only.
+ * @property-read bool $isForbidden Whether this response indicates the current request is forbidden. This
+ * property is read-only.
+ * @property-read bool $isInformational Whether this response is informational. This property is read-only.
+ * @property-read bool $isInvalid Whether this response has a valid [[statusCode]]. This property is
+ * read-only.
+ * @property-read bool $isNotFound Whether this response indicates the currently requested resource is not
+ * found. This property is read-only.
+ * @property-read bool $isOk Whether this response is OK. This property is read-only.
+ * @property-read bool $isRedirection Whether this response is a redirection. This property is read-only.
+ * @property-read bool $isServerError Whether this response indicates a server error. This property is
+ * read-only.
+ * @property-read bool $isSuccessful Whether this response is successful. This property is read-only.
+ * @property int $statusCode The HTTP status code to send with the response.
+ * @property-write \Exception|\Error|\Throwable $statusCodeByException The exception object. This property is
+ * write-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Carsten Brandt <mail@cebe.cc>
+ * @since 2.0
+ */
+class Response extends \yii\base\Response
+{
+    /**
+     * @event \yii\base\Event an event that is triggered at the beginning of [[send()]].
+     */
+    const EVENT_BEFORE_SEND = 'beforeSend';
+    /**
+     * @event \yii\base\Event an event that is triggered at the end of [[send()]].
+     */
+    const EVENT_AFTER_SEND = 'afterSend';
+    /**
+     * @event \yii\base\Event an event that is triggered right after [[prepare()]] is called in [[send()]].
+     * You may respond to this event to filter the response content before it is sent to the client.
+     */
+    const EVENT_AFTER_PREPARE = 'afterPrepare';
+    const FORMAT_RAW = 'raw';
+    const FORMAT_HTML = 'html';
+    const FORMAT_JSON = 'json';
+    const FORMAT_JSONP = 'jsonp';
+    const FORMAT_XML = 'xml';
+
+    /**
+     * @var string the response format. This determines how to convert [[data]] into [[content]]
+     * when the latter is not set. The value of this property must be one of the keys declared in the [[formatters]] array.
+     * By default, the following formats are supported:
+     *
+     * - [[FORMAT_RAW]]: the data will be treated as the response content without any conversion.
+     *   No extra HTTP header will be added.
+     * - [[FORMAT_HTML]]: the data will be treated as the response content without any conversion.
+     *   The "Content-Type" header will set as "text/html".
+     * - [[FORMAT_JSON]]: the data will be converted into JSON format, and the "Content-Type"
+     *   header will be set as "application/json".
+     * - [[FORMAT_JSONP]]: the data will be converted into JSONP format, and the "Content-Type"
+     *   header will be set as "text/javascript". Note that in this case `$data` must be an array
+     *   with "data" and "callback" elements. The former refers to the actual data to be sent,
+     *   while the latter refers to the name of the JavaScript callback.
+     * - [[FORMAT_XML]]: the data will be converted into XML format. Please refer to [[XmlResponseFormatter]]
+     *   for more details.
+     *
+     * You may customize the formatting process or support additional formats by configuring [[formatters]].
+     * @see formatters
+     */
+    public $format = self::FORMAT_HTML;
+    /**
+     * @var string the MIME type (e.g. `application/json`) from the request ACCEPT header chosen for this response.
+     * This property is mainly set by [[\yii\filters\ContentNegotiator]].
+     */
+    public $acceptMimeType;
+    /**
+     * @var array the parameters (e.g. `['q' => 1, 'version' => '1.0']`) associated with the [[acceptMimeType|chosen MIME type]].
+     * This is a list of name-value pairs associated with [[acceptMimeType]] from the ACCEPT HTTP header.
+     * This property is mainly set by [[\yii\filters\ContentNegotiator]].
+     */
+    public $acceptParams = [];
+    /**
+     * @var array the formatters for converting data into the response content of the specified [[format]].
+     * The array keys are the format names, and the array values are the corresponding configurations
+     * for creating the formatter objects.
+     * @see format
+     * @see defaultFormatters
+     */
+    public $formatters = [];
+    /**
+     * @var mixed the original response data. When this is not null, it will be converted into [[content]]
+     * according to [[format]] when the response is being sent out.
+     * @see content
+     */
+    public $data;
+    /**
+     * @var string the response content. When [[data]] is not null, it will be converted into [[content]]
+     * according to [[format]] when the response is being sent out.
+     * @see data
+     */
+    public $content;
+    /**
+     * @var resource|array|callable the stream to be sent. This can be a stream handle or an array of stream handle,
+     * the begin position and the end position. Alternatively it can be set to a callable, which returns
+     * (or [yields](https://www.php.net/manual/en/language.generators.syntax.php)) an array of strings that should
+     * be echoed and flushed out one by one.
+     *
+     * Note that when this property is set, the [[data]] and [[content]] properties will be ignored by [[send()]].
+     */
+    public $stream;
+    /**
+     * @var string the charset of the text response. If not set, it will use
+     * the value of [[Application::charset]].
+     */
+    public $charset;
+    /**
+     * @var string the HTTP status description that comes together with the status code.
+     * @see httpStatuses
+     */
+    public $statusText = 'OK';
+    /**
+     * @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`,
+     * or '1.1' if that is not available.
+     */
+    public $version;
+    /**
+     * @var bool whether the response has been sent. If this is true, calling [[send()]] will do nothing.
+     */
+    public $isSent = false;
+    /**
+     * @var array list of HTTP status codes and the corresponding texts
+     */
+    public static $httpStatuses = [
+        100 => 'Continue',
+        101 => 'Switching Protocols',
+        102 => 'Processing',
+        118 => 'Connection timed out',
+        200 => 'OK',
+        201 => 'Created',
+        202 => 'Accepted',
+        203 => 'Non-Authoritative',
+        204 => 'No Content',
+        205 => 'Reset Content',
+        206 => 'Partial Content',
+        207 => 'Multi-Status',
+        208 => 'Already Reported',
+        210 => 'Content Different',
+        226 => 'IM Used',
+        300 => 'Multiple Choices',
+        301 => 'Moved Permanently',
+        302 => 'Found',
+        303 => 'See Other',
+        304 => 'Not Modified',
+        305 => 'Use Proxy',
+        306 => 'Reserved',
+        307 => 'Temporary Redirect',
+        308 => 'Permanent Redirect',
+        310 => 'Too many Redirect',
+        400 => 'Bad Request',
+        401 => 'Unauthorized',
+        402 => 'Payment Required',
+        403 => 'Forbidden',
+        404 => 'Not Found',
+        405 => 'Method Not Allowed',
+        406 => 'Not Acceptable',
+        407 => 'Proxy Authentication Required',
+        408 => 'Request Time-out',
+        409 => 'Conflict',
+        410 => 'Gone',
+        411 => 'Length Required',
+        412 => 'Precondition Failed',
+        413 => 'Request Entity Too Large',
+        414 => 'Request-URI Too Long',
+        415 => 'Unsupported Media Type',
+        416 => 'Requested range unsatisfiable',
+        417 => 'Expectation failed',
+        418 => 'I\'m a teapot',
+        421 => 'Misdirected Request',
+        422 => 'Unprocessable entity',
+        423 => 'Locked',
+        424 => 'Method failure',
+        425 => 'Unordered Collection',
+        426 => 'Upgrade Required',
+        428 => 'Precondition Required',
+        429 => 'Too Many Requests',
+        431 => 'Request Header Fields Too Large',
+        449 => 'Retry With',
+        450 => 'Blocked by Windows Parental Controls',
+        451 => 'Unavailable For Legal Reasons',
+        500 => 'Internal Server Error',
+        501 => 'Not Implemented',
+        502 => 'Bad Gateway or Proxy Error',
+        503 => 'Service Unavailable',
+        504 => 'Gateway Time-out',
+        505 => 'HTTP Version not supported',
+        507 => 'Insufficient storage',
+        508 => 'Loop Detected',
+        509 => 'Bandwidth Limit Exceeded',
+        510 => 'Not Extended',
+        511 => 'Network Authentication Required',
+    ];
+
+    /**
+     * @var int the HTTP status code to send with the response.
+     */
+    private $_statusCode = 200;
+    /**
+     * @var HeaderCollection
+     */
+    private $_headers;
+
+
+    /**
+     * Initializes this component.
+     */
+    public function init()
+    {
+        if ($this->version === null) {
+            if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0') {
+                $this->version = '1.0';
+            } else {
+                $this->version = '1.1';
+            }
+        }
+        if ($this->charset === null) {
+            $this->charset = Yii::$app->charset;
+        }
+        $this->formatters = array_merge($this->defaultFormatters(), $this->formatters);
+    }
+
+    /**
+     * @return int the HTTP status code to send with the response.
+     */
+    public function getStatusCode()
+    {
+        return $this->_statusCode;
+    }
+
+    /**
+     * Sets the response status code.
+     * This method will set the corresponding status text if `$text` is null.
+     * @param int $value the status code
+     * @param string $text the status text. If not set, it will be set automatically based on the status code.
+     * @throws InvalidArgumentException if the status code is invalid.
+     * @return $this the response object itself
+     */
+    public function setStatusCode($value, $text = null)
+    {
+        if ($value === null) {
+            $value = 200;
+        }
+        $this->_statusCode = (int) $value;
+        if ($this->getIsInvalid()) {
+            throw new InvalidArgumentException("The HTTP status code is invalid: $value");
+        }
+        if ($text === null) {
+            $this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : '';
+        } else {
+            $this->statusText = $text;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Sets the response status code based on the exception.
+     * @param \Exception|\Error|\Throwable $e the exception object.
+     * @throws InvalidArgumentException if the status code is invalid.
+     * @return $this the response object itself
+     * @since 2.0.12
+     */
+    public function setStatusCodeByException($e)
+    {
+        if ($e instanceof HttpException) {
+            $this->setStatusCode($e->statusCode);
+        } else {
+            $this->setStatusCode(500);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Returns the header collection.
+     * The header collection contains the currently registered HTTP headers.
+     * @return HeaderCollection the header collection
+     */
+    public function getHeaders()
+    {
+        if ($this->_headers === null) {
+            $this->_headers = new HeaderCollection();
+        }
+
+        return $this->_headers;
+    }
+
+    /**
+     * Sends the response to the client.
+     */
+    public function send()
+    {
+        if ($this->isSent) {
+            return;
+        }
+        $this->trigger(self::EVENT_BEFORE_SEND);
+        $this->prepare();
+        $this->trigger(self::EVENT_AFTER_PREPARE);
+        $this->sendHeaders();
+        $this->sendContent();
+        $this->trigger(self::EVENT_AFTER_SEND);
+        $this->isSent = true;
+    }
+
+    /**
+     * Clears the headers, cookies, content, status code of the response.
+     */
+    public function clear()
+    {
+        $this->_headers = null;
+        $this->_cookies = null;
+        $this->_statusCode = 200;
+        $this->statusText = 'OK';
+        $this->data = null;
+        $this->stream = null;
+        $this->content = null;
+        $this->isSent = false;
+    }
+
+    /**
+     * Sends the response headers to the client.
+     */
+    protected function sendHeaders()
+    {
+        if (headers_sent($file, $line)) {
+            throw new HeadersAlreadySentException($file, $line);
+        }
+        if ($this->_headers) {
+            foreach ($this->getHeaders() as $name => $values) {
+                $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
+                // set replace for first occurrence of header but false afterwards to allow multiple
+                $replace = true;
+                foreach ($values as $value) {
+                    header("$name: $value", $replace);
+                    $replace = false;
+                }
+            }
+        }
+        $statusCode = $this->getStatusCode();
+        header("HTTP/{$this->version} {$statusCode} {$this->statusText}");
+        $this->sendCookies();
+    }
+
+    /**
+     * Sends the cookies to the client.
+     */
+    protected function sendCookies()
+    {
+        if ($this->_cookies === null) {
+            return;
+        }
+        $request = Yii::$app->getRequest();
+        if ($request->enableCookieValidation) {
+            if ($request->cookieValidationKey == '') {
+                throw new InvalidConfigException(get_class($request) . '::cookieValidationKey must be configured with a secret key.');
+            }
+            $validationKey = $request->cookieValidationKey;
+        }
+        foreach ($this->getCookies() as $cookie) {
+            $value = $cookie->value;
+            if ($cookie->expire != 1 && isset($validationKey)) {
+                $value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey);
+            }
+            if (PHP_VERSION_ID >= 70300) {
+                setcookie($cookie->name, $value, [
+                    'expires' => $cookie->expire,
+                    'path' => $cookie->path,
+                    'domain' => $cookie->domain,
+                    'secure' => $cookie->secure,
+                    'httpOnly' => $cookie->httpOnly,
+                    'sameSite' => !empty($cookie->sameSite) ? $cookie->sameSite : null,
+                ]);
+            } else {
+                // Work around for setting sameSite cookie prior PHP 7.3
+                // https://stackoverflow.com/questions/39750906/php-setcookie-samesite-strict/46971326#46971326
+                $cookiePath = $cookie->path;
+                if (!is_null($cookie->sameSite)) {
+                    $cookiePath .= '; samesite=' . $cookie->sameSite;
+                }
+                setcookie($cookie->name, $value, $cookie->expire, $cookiePath, $cookie->domain, $cookie->secure, $cookie->httpOnly);
+            }
+        }
+    }
+
+    /**
+     * Sends the response content to the client.
+     */
+    protected function sendContent()
+    {
+        if ($this->stream === null) {
+            echo $this->content;
+
+            return;
+        }
+
+        // Try to reset time limit for big files
+        if (!function_exists('set_time_limit') || !@set_time_limit(0)) {
+            Yii::warning('set_time_limit() is not available', __METHOD__);
+        }
+
+        if (is_callable($this->stream)) {
+            $data = call_user_func($this->stream);
+            foreach ($data as $datum) {
+                echo $datum;
+                flush();
+            }
+            return;
+        }
+
+        $chunkSize = 8 * 1024 * 1024; // 8MB per chunk
+
+        if (is_array($this->stream)) {
+            list($handle, $begin, $end) = $this->stream;
+
+            // only seek if stream is seekable
+            if ($this->isSeekable($handle)) {
+                fseek($handle, $begin);
+            }
+
+            while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
+                if ($pos + $chunkSize > $end) {
+                    $chunkSize = $end - $pos + 1;
+                }
+                echo fread($handle, $chunkSize);
+                flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
+            }
+            fclose($handle);
+        } else {
+            while (!feof($this->stream)) {
+                echo fread($this->stream, $chunkSize);
+                flush();
+            }
+            fclose($this->stream);
+        }
+    }
+
+    /**
+     * Sends a file to the browser.
+     *
+     * Note that this method only prepares the response for file sending. The file is not sent
+     * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
+     *
+     * The following is an example implementation of a controller action that allows requesting files from a directory
+     * that is not accessible from web:
+     *
+     * ```php
+     * public function actionFile($filename)
+     * {
+     *     $storagePath = Yii::getAlias('@app/files');
+     *
+     *     // check filename for allowed chars (do not allow ../ to avoid security issue: downloading arbitrary files)
+     *     if (!preg_match('/^[a-z0-9]+\.[a-z0-9]+$/i', $filename) || !is_file("$storagePath/$filename")) {
+     *         throw new \yii\web\NotFoundHttpException('The file does not exists.');
+     *     }
+     *     return Yii::$app->response->sendFile("$storagePath/$filename", $filename);
+     * }
+     * ```
+     *
+     * @param string $filePath the path of the file to be sent.
+     * @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`.
+     * @param array $options additional options for sending the file. The following options are supported:
+     *
+     *  - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath`
+     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
+     *    meaning a download dialog will pop up.
+     *
+     * @return $this the response object itself
+     * @see sendContentAsFile()
+     * @see sendStreamAsFile()
+     * @see xSendFile()
+     */
+    public function sendFile($filePath, $attachmentName = null, $options = [])
+    {
+        if (!isset($options['mimeType'])) {
+            $options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath);
+        }
+        if ($attachmentName === null) {
+            $attachmentName = basename($filePath);
+        }
+        $handle = fopen($filePath, 'rb');
+        $this->sendStreamAsFile($handle, $attachmentName, $options);
+
+        return $this;
+    }
+
+    /**
+     * Sends the specified content as a file to the browser.
+     *
+     * Note that this method only prepares the response for file sending. The file is not sent
+     * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
+     *
+     * @param string $content the content to be sent. The existing [[content]] will be discarded.
+     * @param string $attachmentName the file name shown to the user.
+     * @param array $options additional options for sending the file. The following options are supported:
+     *
+     *  - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'.
+     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
+     *    meaning a download dialog will pop up.
+     *
+     * @return $this the response object itself
+     * @throws RangeNotSatisfiableHttpException if the requested range is not satisfiable
+     * @see sendFile() for an example implementation.
+     */
+    public function sendContentAsFile($content, $attachmentName, $options = [])
+    {
+        $headers = $this->getHeaders();
+
+        $contentLength = StringHelper::byteLength($content);
+        $range = $this->getHttpRange($contentLength);
+
+        if ($range === false) {
+            $headers->set('Content-Range', "bytes */$contentLength");
+            throw new RangeNotSatisfiableHttpException();
+        }
+
+        list($begin, $end) = $range;
+        if ($begin != 0 || $end != $contentLength - 1) {
+            $this->setStatusCode(206);
+            $headers->set('Content-Range', "bytes $begin-$end/$contentLength");
+            $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1);
+        } else {
+            $this->setStatusCode(200);
+            $this->content = $content;
+        }
+
+        $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream';
+        $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
+
+        $this->format = self::FORMAT_RAW;
+
+        return $this;
+    }
+
+    /**
+     * Sends the specified stream as a file to the browser.
+     *
+     * Note that this method only prepares the response for file sending. The file is not sent
+     * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
+     *
+     * @param resource $handle the handle of the stream to be sent.
+     * @param string $attachmentName the file name shown to the user.
+     * @param array $options additional options for sending the file. The following options are supported:
+     *
+     *  - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'.
+     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
+     *    meaning a download dialog will pop up.
+     *  - `fileSize`: the size of the content to stream this is useful when size of the content is known
+     *    and the content is not seekable. Defaults to content size using `ftell()`.
+     *    This option is available since version 2.0.4.
+     *
+     * @return $this the response object itself
+     * @throws RangeNotSatisfiableHttpException if the requested range is not satisfiable
+     * @see sendFile() for an example implementation.
+     */
+    public function sendStreamAsFile($handle, $attachmentName, $options = [])
+    {
+        $headers = $this->getHeaders();
+        if (isset($options['fileSize'])) {
+            $fileSize = $options['fileSize'];
+        } else {
+            if ($this->isSeekable($handle)) {
+                fseek($handle, 0, SEEK_END);
+                $fileSize = ftell($handle);
+            } else {
+                $fileSize = 0;
+            }
+        }
+
+        $range = $this->getHttpRange($fileSize);
+        if ($range === false) {
+            $headers->set('Content-Range', "bytes */$fileSize");
+            throw new RangeNotSatisfiableHttpException();
+        }
+
+        list($begin, $end) = $range;
+        if ($begin != 0 || $end != $fileSize - 1) {
+            $this->setStatusCode(206);
+            $headers->set('Content-Range', "bytes $begin-$end/$fileSize");
+        } else {
+            $this->setStatusCode(200);
+        }
+
+        $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream';
+        $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
+
+        $this->format = self::FORMAT_RAW;
+        $this->stream = [$handle, $begin, $end];
+
+        return $this;
+    }
+
+    /**
+     * Sets a default set of HTTP headers for file downloading purpose.
+     * @param string $attachmentName the attachment file name
+     * @param string $mimeType the MIME type for the response. If null, `Content-Type` header will NOT be set.
+     * @param bool $inline whether the browser should open the file within the browser window. Defaults to false,
+     * meaning a download dialog will pop up.
+     * @param int $contentLength the byte length of the file being downloaded. If null, `Content-Length` header will NOT be set.
+     * @return $this the response object itself
+     */
+    public function setDownloadHeaders($attachmentName, $mimeType = null, $inline = false, $contentLength = null)
+    {
+        $headers = $this->getHeaders();
+
+        $disposition = $inline ? 'inline' : 'attachment';
+        $headers->setDefault('Pragma', 'public')
+            ->setDefault('Accept-Ranges', 'bytes')
+            ->setDefault('Expires', '0')
+            ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+            ->setDefault('Content-Disposition', $this->getDispositionHeaderValue($disposition, $attachmentName));
+
+        if ($mimeType !== null) {
+            $headers->setDefault('Content-Type', $mimeType);
+        }
+
+        if ($contentLength !== null) {
+            $headers->setDefault('Content-Length', $contentLength);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Determines the HTTP range given in the request.
+     * @param int $fileSize the size of the file that will be used to validate the requested HTTP range.
+     * @return array|bool the range (begin, end), or false if the range request is invalid.
+     */
+    protected function getHttpRange($fileSize)
+    {
+        $rangeHeader = Yii::$app->getRequest()->getHeaders()->get('Range', '-');
+        if ($rangeHeader === '-') {
+            return [0, $fileSize - 1];
+        }
+        if (!preg_match('/^bytes=(\d*)-(\d*)$/', $rangeHeader, $matches)) {
+            return false;
+        }
+        if ($matches[1] === '') {
+            $start = $fileSize - $matches[2];
+            $end = $fileSize - 1;
+        } elseif ($matches[2] !== '') {
+            $start = $matches[1];
+            $end = $matches[2];
+            if ($end >= $fileSize) {
+                $end = $fileSize - 1;
+            }
+        } else {
+            $start = $matches[1];
+            $end = $fileSize - 1;
+        }
+        if ($start < 0 || $start > $end) {
+            return false;
+        }
+
+        return [$start, $end];
+    }
+
+    /**
+     * Sends existing file to a browser as a download using x-sendfile.
+     *
+     * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver
+     * that in turn processes the request, this way eliminating the need to perform tasks like reading the file
+     * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great
+     * increase in performance as the web application is allowed to terminate earlier while the webserver is
+     * handling the request.
+     *
+     * The request is sent to the server through a special non-standard HTTP-header.
+     * When the web server encounters the presence of such header it will discard all output and send the file
+     * specified by that header using web server internals including all optimizations like caching-headers.
+     *
+     * As this header directive is non-standard different directives exists for different web servers applications:
+     *
+     * - Apache: [X-Sendfile](http://tn123.org/mod_xsendfile)
+     * - Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
+     * - Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
+     * - Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile)
+     * - Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile)
+     *
+     * So for this method to work the X-SENDFILE option/module should be enabled by the web server and
+     * a proper xHeader should be sent.
+     *
+     * **Note**
+     *
+     * This option allows to download files that are not under web folders, and even files that are otherwise protected
+     * (deny from all) like `.htaccess`.
+     *
+     * **Side effects**
+     *
+     * If this option is disabled by the web server, when this method is called a download configuration dialog
+     * will open but the downloaded file will have 0 bytes.
+     *
+     * **Known issues**
+     *
+     * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
+     * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site
+     * is either unavailable or cannot be found.". You can work around this problem by removing the `Pragma`-header.
+     *
+     * **Example**
+     *
+     * ```php
+     * Yii::$app->response->xSendFile('/home/user/Pictures/picture1.jpg');
+     * ```
+     *
+     * @param string $filePath file name with full path
+     * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`.
+     * @param array $options additional options for sending the file. The following options are supported:
+     *
+     *  - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath`
+     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
+     *    meaning a download dialog will pop up.
+     *  - xHeader: string, the name of the x-sendfile header. Defaults to "X-Sendfile".
+     *
+     * @return $this the response object itself
+     * @see sendFile()
+     */
+    public function xSendFile($filePath, $attachmentName = null, $options = [])
+    {
+        if ($attachmentName === null) {
+            $attachmentName = basename($filePath);
+        }
+        if (isset($options['mimeType'])) {
+            $mimeType = $options['mimeType'];
+        } elseif (($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
+            $mimeType = 'application/octet-stream';
+        }
+        if (isset($options['xHeader'])) {
+            $xHeader = $options['xHeader'];
+        } else {
+            $xHeader = 'X-Sendfile';
+        }
+
+        $disposition = empty($options['inline']) ? 'attachment' : 'inline';
+        $this->getHeaders()
+            ->setDefault($xHeader, $filePath)
+            ->setDefault('Content-Type', $mimeType)
+            ->setDefault('Content-Disposition', $this->getDispositionHeaderValue($disposition, $attachmentName));
+
+        $this->format = self::FORMAT_RAW;
+
+        return $this;
+    }
+
+    /**
+     * Returns Content-Disposition header value that is safe to use with both old and new browsers.
+     *
+     * Fallback name:
+     *
+     * - Causes issues if contains non-ASCII characters with codes less than 32 or more than 126.
+     * - Causes issues if contains urlencoded characters (starting with `%`) or `%` character. Some browsers interpret
+     *   `filename="X"` as urlencoded name, some don't.
+     * - Causes issues if contains path separator characters such as `\` or `/`.
+     * - Since value is wrapped with `"`, it should be escaped as `\"`.
+     * - Since input could contain non-ASCII characters, fallback is obtained by transliteration.
+     *
+     * UTF name:
+     *
+     * - Causes issues if contains path separator characters such as `\` or `/`.
+     * - Should be urlencoded since headers are ASCII-only.
+     * - Could be omitted if it exactly matches fallback name.
+     *
+     * @param string $disposition
+     * @param string $attachmentName
+     * @return string
+     *
+     * @since 2.0.10
+     */
+    protected function getDispositionHeaderValue($disposition, $attachmentName)
+    {
+        $fallbackName = str_replace(
+            ['%', '/', '\\', '"', "\x7F"],
+            ['_', '_', '_', '\\"', '_'],
+            Inflector::transliterate($attachmentName, Inflector::TRANSLITERATE_LOOSE)
+        );
+        $utfName = rawurlencode(str_replace(['%', '/', '\\'], '', $attachmentName));
+
+        $dispositionHeader = "{$disposition}; filename=\"{$fallbackName}\"";
+        if ($utfName !== $fallbackName) {
+            $dispositionHeader .= "; filename*=utf-8''{$utfName}";
+        }
+
+        return $dispositionHeader;
+    }
+
+    /**
+     * Redirects the browser to the specified URL.
+     *
+     * This method adds a "Location" header to the current response. Note that it does not send out
+     * the header until [[send()]] is called. In a controller action you may use this method as follows:
+     *
+     * ```php
+     * return Yii::$app->getResponse()->redirect($url);
+     * ```
+     *
+     * In other places, if you want to send out the "Location" header immediately, you should use
+     * the following code:
+     *
+     * ```php
+     * Yii::$app->getResponse()->redirect($url)->send();
+     * return;
+     * ```
+     *
+     * In AJAX mode, this normally will not work as expected unless there are some
+     * client-side JavaScript code handling the redirection. To help achieve this goal,
+     * this method will send out a "X-Redirect" header instead of "Location".
+     *
+     * If you use the "yii" JavaScript module, it will handle the AJAX redirection as
+     * described above. Otherwise, you should write the following JavaScript code to
+     * handle the redirection:
+     *
+     * ```javascript
+     * $document.ajaxComplete(function (event, xhr, settings) {
+     *     var url = xhr && xhr.getResponseHeader('X-Redirect');
+     *     if (url) {
+     *         window.location = url;
+     *     }
+     * });
+     * ```
+     *
+     * @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]`).
+     *   Note that the route is with respect to the whole application, instead of relative to a controller or module.
+     *   [[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
+     * @param bool $checkAjax whether to specially handle AJAX (and PJAX) requests. Defaults to true,
+     * meaning if the current request is an AJAX or PJAX request, then calling this method will cause the browser
+     * to redirect to the given URL. If this is false, a `Location` header will be sent, which when received as
+     * an AJAX/PJAX response, may NOT cause browser redirection.
+     * Takes effect only when request header `X-Ie-Redirect-Compatibility` is absent.
+     * @return $this the response object itself
+     */
+    public function redirect($url, $statusCode = 302, $checkAjax = true)
+    {
+        if (is_array($url) && isset($url[0])) {
+            // ensure the route is absolute
+            $url[0] = '/' . ltrim($url[0], '/');
+        }
+        $request = Yii::$app->getRequest();
+        $url = Url::to($url);
+        if (strncmp($url, '/', 1) === 0 && strncmp($url, '//', 2) !== 0) {
+            $url = $request->getHostInfo() . $url;
+        }
+
+        if ($checkAjax) {
+            if ($request->getIsAjax()) {
+                if (in_array($statusCode, [301, 302]) && preg_match('/Trident\/|MSIE[ ]/', $request->userAgent)) {
+                    $statusCode = 200;
+                }
+                if ($request->getIsPjax()) {
+                    $this->getHeaders()->set('X-Pjax-Url', $url);
+                } else {
+                    $this->getHeaders()->set('X-Redirect', $url);
+                }
+            } else {
+                $this->getHeaders()->set('Location', $url);
+            }
+        } else {
+            $this->getHeaders()->set('Location', $url);
+        }
+
+        $this->setStatusCode($statusCode);
+
+        return $this;
+    }
+
+    /**
+     * Refreshes the current page.
+     * The effect of this method call is the same as the user pressing the refresh button of his browser
+     * (without re-posting data).
+     *
+     * In a controller action you may use this method like this:
+     *
+     * ```php
+     * return Yii::$app->getResponse()->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 $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
+    }
+
+    private $_cookies;
+
+    /**
+     * Returns the cookie collection.
+     *
+     * Through the returned cookie collection, you add or remove cookies as follows,
+     *
+     * ```php
+     * // add a cookie
+     * $response->cookies->add(new Cookie([
+     *     'name' => $name,
+     *     'value' => $value,
+     * ]);
+     *
+     * // remove a cookie
+     * $response->cookies->remove('name');
+     * // alternatively
+     * unset($response->cookies['name']);
+     * ```
+     *
+     * @return CookieCollection the cookie collection.
+     */
+    public function getCookies()
+    {
+        if ($this->_cookies === null) {
+            $this->_cookies = new CookieCollection();
+        }
+
+        return $this->_cookies;
+    }
+
+    /**
+     * @return bool whether this response has a valid [[statusCode]].
+     */
+    public function getIsInvalid()
+    {
+        return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
+    }
+
+    /**
+     * @return bool whether this response is informational
+     */
+    public function getIsInformational()
+    {
+        return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
+    }
+
+    /**
+     * @return bool whether this response is successful
+     */
+    public function getIsSuccessful()
+    {
+        return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
+    }
+
+    /**
+     * @return bool whether this response is a redirection
+     */
+    public function getIsRedirection()
+    {
+        return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
+    }
+
+    /**
+     * @return bool whether this response indicates a client error
+     */
+    public function getIsClientError()
+    {
+        return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
+    }
+
+    /**
+     * @return bool whether this response indicates a server error
+     */
+    public function getIsServerError()
+    {
+        return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
+    }
+
+    /**
+     * @return bool whether this response is OK
+     */
+    public function getIsOk()
+    {
+        return $this->getStatusCode() == 200;
+    }
+
+    /**
+     * @return bool whether this response indicates the current request is forbidden
+     */
+    public function getIsForbidden()
+    {
+        return $this->getStatusCode() == 403;
+    }
+
+    /**
+     * @return bool whether this response indicates the currently requested resource is not found
+     */
+    public function getIsNotFound()
+    {
+        return $this->getStatusCode() == 404;
+    }
+
+    /**
+     * @return bool whether this response is empty
+     */
+    public function getIsEmpty()
+    {
+        return in_array($this->getStatusCode(), [201, 204, 304]);
+    }
+
+    /**
+     * @return array the formatters that are supported by default
+     */
+    protected function defaultFormatters()
+    {
+        return [
+            self::FORMAT_HTML => [
+                'class' => 'yii\web\HtmlResponseFormatter',
+            ],
+            self::FORMAT_XML => [
+                'class' => 'yii\web\XmlResponseFormatter',
+            ],
+            self::FORMAT_JSON => [
+                'class' => 'yii\web\JsonResponseFormatter',
+            ],
+            self::FORMAT_JSONP => [
+                'class' => 'yii\web\JsonResponseFormatter',
+                'useJsonp' => true,
+            ],
+        ];
+    }
+
+    /**
+     * Prepares for sending the response.
+     * The default implementation will convert [[data]] into [[content]] and set headers accordingly.
+     * @throws InvalidConfigException if the formatter for the specified format is invalid or [[format]] is not supported
+     *
+     * @see https://tools.ietf.org/html/rfc7231#page-53
+     * @see https://tools.ietf.org/html/rfc7232#page-18
+     */
+    protected function prepare()
+    {
+        if (in_array($this->getStatusCode(), [204, 304])) {
+            // A 204/304 response cannot contain a message body according to rfc7231/rfc7232
+            $this->content = '';
+            $this->stream = null;
+            return;
+        }
+
+        if ($this->stream !== null) {
+            return;
+        }
+
+        if (isset($this->formatters[$this->format])) {
+            $formatter = $this->formatters[$this->format];
+            if (!is_object($formatter)) {
+                $this->formatters[$this->format] = $formatter = Yii::createObject($formatter);
+            }
+            if ($formatter instanceof ResponseFormatterInterface) {
+                $formatter->format($this);
+            } else {
+                throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
+            }
+        } elseif ($this->format === self::FORMAT_RAW) {
+            if ($this->data !== null) {
+                $this->content = $this->data;
+            }
+        } else {
+            throw new InvalidConfigException("Unsupported response format: {$this->format}");
+        }
+
+        if (is_array($this->content)) {
+            throw new InvalidArgumentException('Response content must not be an array.');
+        } elseif (is_object($this->content)) {
+            if (method_exists($this->content, '__toString')) {
+                $this->content = $this->content->__toString();
+            } else {
+                throw new InvalidArgumentException('Response content must be a string or an object implementing __toString().');
+            }
+        }
+    }
+
+    /**
+     * Checks if a stream is seekable
+     *
+     * @param $handle
+     * @return bool
+     */
+    private function isSeekable($handle)
+    {
+        if (!is_resource($handle)) {
+            return true;
+        }
+
+        $metaData = stream_get_meta_data($handle);
+        return isset($metaData['seekable']) && $metaData['seekable'] === true;
+    }
+}

+ 23 - 0
vendor/yiisoft/yii2/web/ResponseFormatterInterface.php

@@ -0,0 +1,23 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * ResponseFormatterInterface specifies the interface needed to format a response before it is sent out.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+interface ResponseFormatterInterface
+{
+    /**
+     * Formats the specified response.
+     * @param Response $response the response to be formatted.
+     */
+    public function format($response);
+}

+ 29 - 0
vendor/yiisoft/yii2/web/ServerErrorHttpException.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * ServerErrorHttpException represents an "Internal Server Error" HTTP exception with status code 500.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.6.1
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ServerErrorHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(500, $message, $code, $previous);
+    }
+}

+ 1069 - 0
vendor/yiisoft/yii2/web/Session.php

@@ -0,0 +1,1069 @@
+<?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\Component;
+use yii\base\InvalidArgumentException;
+use yii\base\InvalidConfigException;
+
+/**
+ * Session provides session data management and the related configurations.
+ *
+ * Session is a Web application component that can be accessed via `Yii::$app->session`.
+ *
+ * To start the session, call [[open()]]; To complete and send out session data, call [[close()]];
+ * To destroy the session, call [[destroy()]].
+ *
+ * Session can be used like an array to set and get session data. For example,
+ *
+ * ```php
+ * $session = new Session;
+ * $session->open();
+ * $value1 = $session['name1'];  // get session variable 'name1'
+ * $value2 = $session['name2'];  // get session variable 'name2'
+ * foreach ($session as $name => $value) // traverse all session variables
+ * $session['name3'] = $value3;  // set session variable 'name3'
+ * ```
+ *
+ * Session can be extended to support customized session storage.
+ * To do so, override [[useCustomStorage]] so that it returns true, and
+ * override these methods with the actual logic about using custom storage:
+ * [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
+ * [[destroySession()]] and [[gcSession()]].
+ *
+ * Session also supports a special type of session data, called *flash messages*.
+ * A flash message is available only in the current request and the next request.
+ * After that, it will be deleted automatically. Flash messages are particularly
+ * useful for displaying confirmation messages. To use flash messages, simply
+ * call methods such as [[setFlash()]], [[getFlash()]].
+ *
+ * For more details and usage information on Session, see the [guide article on sessions](guide:runtime-sessions-cookies).
+ *
+ * @property-read array $allFlashes Flash messages (key => message or key => [message1, message2]). This
+ * property is read-only.
+ * @property-read string $cacheLimiter Current cache limiter. This property is read-only.
+ * @property-read array $cookieParams The session cookie parameters. This property is read-only.
+ * @property-read int $count The number of session variables. This property is read-only.
+ * @property-write string $flash The key identifying the flash message. Note that flash messages and normal
+ * session variables share the same name space. If you have a normal session variable using the same name, its
+ * value will be overwritten by this method. This property is write-only.
+ * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is
+ * started on every session initialization.
+ * @property bool $hasSessionId Whether the current request has sent the session ID.
+ * @property string $id The current session ID.
+ * @property-read bool $isActive Whether the session has started. This property is read-only.
+ * @property-read SessionIterator $iterator An iterator for traversing the session variables. This property is
+ * read-only.
+ * @property string $name The current session name.
+ * @property string $savePath The current session save path, defaults to '/tmp'.
+ * @property int $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. The
+ * default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
+ * @property bool|null $useCookies The value indicating whether cookies should be used to store session IDs.
+ * @property-read bool $useCustomStorage Whether to use custom storage. This property is read-only.
+ * @property-read bool $useStrictMode Whether strict mode is enabled or not. This property is read-only.
+ * @property bool $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
+ * false.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
+{
+    /**
+     * @var string|null Holds the original session module (before a custom handler is registered) so that it can be
+     * restored when a Session component without custom handler is used after one that has.
+     */
+    static protected $_originalSessionModule = null;
+    /**
+     * Polyfill for ini directive session.use-strict-mode for PHP < 5.5.2.
+     */
+    static private $_useStrictModePolyfill = false;
+    /**
+     * @var string the name of the session variable that stores the flash message data.
+     */
+    public $flashParam = '__flash';
+    /**
+     * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
+     */
+    public $handler;
+
+    /**
+     * @var string|null Holds the session id in case useStrictMode is enabled and the session id needs to be regenerated
+     */
+    protected $_forceRegenerateId = null;
+
+    /**
+     * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
+     * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly'
+     * @see https://secure.php.net/manual/en/function.session-set-cookie-params.php
+     */
+    private $_cookieParams = ['httponly' => true];
+    /**
+     * @var array|null is used for saving session between recreations due to session parameters update.
+     */
+    private $frozenSessionData;
+
+
+    /**
+     * Initializes the application component.
+     * This method is required by IApplicationComponent and is invoked by application.
+     */
+    public function init()
+    {
+        parent::init();
+        register_shutdown_function([$this, 'close']);
+        if ($this->getIsActive()) {
+            Yii::warning('Session is already started', __METHOD__);
+            $this->updateFlashCounters();
+        }
+    }
+
+    /**
+     * Returns a value indicating whether to use custom session storage.
+     * This method should be overridden to return true by child classes that implement custom session storage.
+     * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
+     * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
+     * @return bool whether to use custom storage.
+     */
+    public function getUseCustomStorage()
+    {
+        return false;
+    }
+
+    /**
+     * Starts the session.
+     */
+    public function open()
+    {
+        if ($this->getIsActive()) {
+            return;
+        }
+
+        $this->registerSessionHandler();
+
+        $this->setCookieParamsInternal();
+
+        YII_DEBUG ? session_start() : @session_start();
+
+        if ($this->getUseStrictMode() && $this->_forceRegenerateId) {
+            $this->regenerateID();
+            $this->_forceRegenerateId = null;
+        }
+
+        if ($this->getIsActive()) {
+            Yii::info('Session started', __METHOD__);
+            $this->updateFlashCounters();
+        } else {
+            $error = error_get_last();
+            $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
+            Yii::error($message, __METHOD__);
+        }
+    }
+
+    /**
+     * Registers session handler.
+     * @throws \yii\base\InvalidConfigException
+     */
+    protected function registerSessionHandler()
+    {
+        $sessionModuleName = session_module_name();
+        if (static::$_originalSessionModule === null) {
+            static::$_originalSessionModule = $sessionModuleName;
+        }
+
+        if ($this->handler !== null) {
+            if (!is_object($this->handler)) {
+                $this->handler = Yii::createObject($this->handler);
+            }
+            if (!$this->handler instanceof \SessionHandlerInterface) {
+                throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
+            }
+            YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
+        } elseif ($this->getUseCustomStorage()) {
+            if (YII_DEBUG) {
+                session_set_save_handler(
+                    [$this, 'openSession'],
+                    [$this, 'closeSession'],
+                    [$this, 'readSession'],
+                    [$this, 'writeSession'],
+                    [$this, 'destroySession'],
+                    [$this, 'gcSession']
+                );
+            } else {
+                @session_set_save_handler(
+                    [$this, 'openSession'],
+                    [$this, 'closeSession'],
+                    [$this, 'readSession'],
+                    [$this, 'writeSession'],
+                    [$this, 'destroySession'],
+                    [$this, 'gcSession']
+                );
+            }
+        } elseif (
+            $sessionModuleName !== static::$_originalSessionModule
+            && static::$_originalSessionModule !== null
+            && static::$_originalSessionModule !== 'user'
+        ) {
+            session_module_name(static::$_originalSessionModule);
+        }
+    }
+
+    /**
+     * Ends the current session and store session data.
+     */
+    public function close()
+    {
+        if ($this->getIsActive()) {
+            YII_DEBUG ? session_write_close() : @session_write_close();
+        }
+
+        $this->_forceRegenerateId = null;
+    }
+
+    /**
+     * Frees all session variables and destroys all data registered to a session.
+     *
+     * This method has no effect when session is not [[getIsActive()|active]].
+     * Make sure to call [[open()]] before calling it.
+     * @see open()
+     * @see isActive
+     */
+    public function destroy()
+    {
+        if ($this->getIsActive()) {
+            $sessionId = session_id();
+            $this->close();
+            $this->setId($sessionId);
+            $this->open();
+            session_unset();
+            session_destroy();
+            $this->setId($sessionId);
+        }
+    }
+
+    /**
+     * @return bool whether the session has started
+     */
+    public function getIsActive()
+    {
+        return session_status() === PHP_SESSION_ACTIVE;
+    }
+
+    private $_hasSessionId;
+
+    /**
+     * Returns a value indicating whether the current request has sent the session ID.
+     * The default implementation will check cookie and $_GET using the session name.
+     * If you send session ID via other ways, you may need to override this method
+     * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
+     * @return bool whether the current request has sent the session ID.
+     */
+    public function getHasSessionId()
+    {
+        if ($this->_hasSessionId === null) {
+            $name = $this->getName();
+            $request = Yii::$app->getRequest();
+            if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
+                $this->_hasSessionId = true;
+            } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
+                $this->_hasSessionId = $request->get($name) != '';
+            } else {
+                $this->_hasSessionId = false;
+            }
+        }
+
+        return $this->_hasSessionId;
+    }
+
+    /**
+     * Sets the value indicating whether the current request has sent the session ID.
+     * This method is provided so that you can override the default way of determining
+     * whether the session ID is sent.
+     * @param bool $value whether the current request has sent the session ID.
+     */
+    public function setHasSessionId($value)
+    {
+        $this->_hasSessionId = $value;
+    }
+
+    /**
+     * Gets the session ID.
+     * This is a wrapper for [PHP session_id()](https://secure.php.net/manual/en/function.session-id.php).
+     * @return string the current session ID
+     */
+    public function getId()
+    {
+        return session_id();
+    }
+
+    /**
+     * Sets the session ID.
+     * This is a wrapper for [PHP session_id()](https://secure.php.net/manual/en/function.session-id.php).
+     * @param string $value the session ID for the current session
+     */
+    public function setId($value)
+    {
+        session_id($value);
+    }
+
+    /**
+     * Updates the current session ID with a newly generated one.
+     *
+     * Please refer to <https://secure.php.net/session_regenerate_id> for more details.
+     *
+     * This method has no effect when session is not [[getIsActive()|active]].
+     * Make sure to call [[open()]] before calling it.
+     *
+     * @param bool $deleteOldSession Whether to delete the old associated session file or not.
+     * @see open()
+     * @see isActive
+     */
+    public function regenerateID($deleteOldSession = false)
+    {
+        if ($this->getIsActive()) {
+            // add @ to inhibit possible warning due to race condition
+            // https://github.com/yiisoft/yii2/pull/1812
+            if (YII_DEBUG && !headers_sent()) {
+                session_regenerate_id($deleteOldSession);
+            } else {
+                @session_regenerate_id($deleteOldSession);
+            }
+        }
+    }
+
+    /**
+     * Gets the name of the current session.
+     * This is a wrapper for [PHP session_name()](https://secure.php.net/manual/en/function.session-name.php).
+     * @return string the current session name
+     */
+    public function getName()
+    {
+        return session_name();
+    }
+
+    /**
+     * Sets the name for the current session.
+     * This is a wrapper for [PHP session_name()](https://secure.php.net/manual/en/function.session-name.php).
+     * @param string $value the session name for the current session, must be an alphanumeric string.
+     * It defaults to "PHPSESSID".
+     */
+    public function setName($value)
+    {
+        $this->freeze();
+        session_name($value);
+        $this->unfreeze();
+    }
+
+    /**
+     * Gets the current session save path.
+     * This is a wrapper for [PHP session_save_path()](https://secure.php.net/manual/en/function.session-save-path.php).
+     * @return string the current session save path, defaults to '/tmp'.
+     */
+    public function getSavePath()
+    {
+        return session_save_path();
+    }
+
+    /**
+     * Sets the current session save path.
+     * This is a wrapper for [PHP session_save_path()](https://secure.php.net/manual/en/function.session-save-path.php).
+     * @param string $value the current session save path. This can be either a directory name or a [path alias](guide:concept-aliases).
+     * @throws InvalidArgumentException if the path is not a valid directory
+     */
+    public function setSavePath($value)
+    {
+        $path = Yii::getAlias($value);
+        if (is_dir($path)) {
+            session_save_path($path);
+        } else {
+            throw new InvalidArgumentException("Session save path is not a valid directory: $value");
+        }
+    }
+
+    /**
+     * @return array the session cookie parameters.
+     * @see https://secure.php.net/manual/en/function.session-get-cookie-params.php
+     */
+    public function getCookieParams()
+    {
+        return array_merge(session_get_cookie_params(), array_change_key_case($this->_cookieParams));
+    }
+
+    /**
+     * Sets the session cookie parameters.
+     * The cookie parameters passed to this method will be merged with the result
+     * of `session_get_cookie_params()`.
+     * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`.
+     * Starting with Yii 2.0.21 `sameSite` is also supported. It requires PHP version 7.3.0 or higher.
+     * For securtiy, an exception will be thrown if `sameSite` is set while using an unsupported version of PHP.
+     * To use this feature across different PHP versions check the version first. E.g.
+     * ```php
+     * [
+     *     'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
+     * ]
+     * ```
+     * See https://www.owasp.org/index.php/SameSite for more information about `sameSite`.
+     *
+     * @throws InvalidArgumentException if the parameters are incomplete.
+     * @see https://secure.php.net/manual/en/function.session-set-cookie-params.php
+     */
+    public function setCookieParams(array $value)
+    {
+        $this->_cookieParams = $value;
+    }
+
+    /**
+     * Sets the session cookie parameters.
+     * This method is called by [[open()]] when it is about to open the session.
+     * @throws InvalidArgumentException if the parameters are incomplete.
+     * @see https://secure.php.net/manual/en/function.session-set-cookie-params.php
+     */
+    private function setCookieParamsInternal()
+    {
+        $data = $this->getCookieParams();
+        if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) {
+            if (PHP_VERSION_ID >= 70300) {
+                session_set_cookie_params($data);
+            } else {
+                if (!empty($data['samesite'])) {
+                    $data['path'] .= '; samesite=' . $data['samesite'];
+                }
+                session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
+            }
+
+        } else {
+            throw new InvalidArgumentException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.');
+        }
+    }
+
+    /**
+     * Returns the value indicating whether cookies should be used to store session IDs.
+     * @return bool|null the value indicating whether cookies should be used to store session IDs.
+     * @see setUseCookies()
+     */
+    public function getUseCookies()
+    {
+        if (ini_get('session.use_cookies') === '0') {
+            return false;
+        } elseif (ini_get('session.use_only_cookies') === '1') {
+            return true;
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets the value indicating whether cookies should be used to store session IDs.
+     *
+     * Three states are possible:
+     *
+     * - true: cookies and only cookies will be used to store session IDs.
+     * - false: cookies will not be used to store session IDs.
+     * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
+     *
+     * @param bool|null $value the value indicating whether cookies should be used to store session IDs.
+     */
+    public function setUseCookies($value)
+    {
+        $this->freeze();
+        if ($value === false) {
+            ini_set('session.use_cookies', '0');
+            ini_set('session.use_only_cookies', '0');
+        } elseif ($value === true) {
+            ini_set('session.use_cookies', '1');
+            ini_set('session.use_only_cookies', '1');
+        } else {
+            ini_set('session.use_cookies', '1');
+            ini_set('session.use_only_cookies', '0');
+        }
+        $this->unfreeze();
+    }
+
+    /**
+     * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
+     */
+    public function getGCProbability()
+    {
+        return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
+    }
+
+    /**
+     * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
+     * @throws InvalidArgumentException if the value is not between 0 and 100.
+     */
+    public function setGCProbability($value)
+    {
+        $this->freeze();
+        if ($value >= 0 && $value <= 100) {
+            // percent * 21474837 / 2147483647 ≈ percent * 0.01
+            ini_set('session.gc_probability', floor($value * 21474836.47));
+            ini_set('session.gc_divisor', 2147483647);
+        } else {
+            throw new InvalidArgumentException('GCProbability must be a value between 0 and 100.');
+        }
+        $this->unfreeze();
+    }
+
+    /**
+     * @return bool whether transparent sid support is enabled or not, defaults to false.
+     */
+    public function getUseTransparentSessionID()
+    {
+        return ini_get('session.use_trans_sid') == 1;
+    }
+
+    /**
+     * @param bool $value whether transparent sid support is enabled or not.
+     */
+    public function setUseTransparentSessionID($value)
+    {
+        $this->freeze();
+        ini_set('session.use_trans_sid', $value ? '1' : '0');
+        $this->unfreeze();
+    }
+
+    /**
+     * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up.
+     * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
+     */
+    public function getTimeout()
+    {
+        return (int) ini_get('session.gc_maxlifetime');
+    }
+
+    /**
+     * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
+     */
+    public function setTimeout($value)
+    {
+        $this->freeze();
+        ini_set('session.gc_maxlifetime', $value);
+        $this->unfreeze();
+    }
+
+    /**
+     * @var bool Whether strict mode is enabled or not.
+     * When `true` this setting prevents the session component to use an uninitialized session ID.
+     * Note: Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.
+     * Warning! Although enabling strict mode is mandatory for secure sessions, the default value of 'session.use-strict-mode' is `0`.
+     * @see https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode
+     * @since 2.0.38
+     */
+    public function setUseStrictMode($value)
+    {
+        if (PHP_VERSION_ID < 50502) {
+            if ($this->getUseCustomStorage() || !$value) {
+                self::$_useStrictModePolyfill = $value;
+            } else {
+                throw new InvalidConfigException('Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.');
+            }
+        } else {
+            $this->freeze();
+            ini_set('session.use_strict_mode', $value ? '1' : '0');
+            $this->unfreeze();
+        }
+    }
+
+    /**
+     * @return bool Whether strict mode is enabled or not.
+     * @see setUseStrictMode()
+     * @since 2.0.38
+     */
+    public function getUseStrictMode()
+    {
+        if (PHP_VERSION_ID < 50502) {
+            return self::$_useStrictModePolyfill;
+        }
+
+        return (bool)ini_get('session.use_strict_mode');
+    }
+
+    /**
+     * Session open handler.
+     * This method should be overridden if [[useCustomStorage]] returns true.
+     * @internal Do not call this method directly.
+     * @param string $savePath session save path
+     * @param string $sessionName session name
+     * @return bool whether session is opened successfully
+     */
+    public function openSession($savePath, $sessionName)
+    {
+        return true;
+    }
+
+    /**
+     * Session close handler.
+     * This method should be overridden if [[useCustomStorage]] returns true.
+     * @internal Do not call this method directly.
+     * @return bool whether session is closed successfully
+     */
+    public function closeSession()
+    {
+        return true;
+    }
+
+    /**
+     * Session read handler.
+     * This method should be overridden if [[useCustomStorage]] returns true.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @return string the session data
+     */
+    public function readSession($id)
+    {
+        return '';
+    }
+
+    /**
+     * Session write handler.
+     * This method should be overridden if [[useCustomStorage]] returns true.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @param string $data session data
+     * @return bool whether session write is successful
+     */
+    public function writeSession($id, $data)
+    {
+        return true;
+    }
+
+    /**
+     * Session destroy handler.
+     * This method should be overridden if [[useCustomStorage]] returns true.
+     * @internal Do not call this method directly.
+     * @param string $id session ID
+     * @return bool whether session is destroyed successfully
+     */
+    public function destroySession($id)
+    {
+        return true;
+    }
+
+    /**
+     * Session GC (garbage collection) handler.
+     * This method should be overridden if [[useCustomStorage]] returns true.
+     * @internal Do not call this method directly.
+     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
+     * @return bool whether session is GCed successfully
+     */
+    public function gcSession($maxLifetime)
+    {
+        return true;
+    }
+
+    /**
+     * Returns an iterator for traversing the session variables.
+     * This method is required by the interface [[\IteratorAggregate]].
+     * @return SessionIterator an iterator for traversing the session variables.
+     */
+    public function getIterator()
+    {
+        $this->open();
+        return new SessionIterator();
+    }
+
+    /**
+     * Returns the number of items in the session.
+     * @return int the number of session variables
+     */
+    public function getCount()
+    {
+        $this->open();
+        return count($_SESSION);
+    }
+
+    /**
+     * Returns the number of items in the session.
+     * This method is required by [[\Countable]] interface.
+     * @return int number of items in the session.
+     */
+    public function count()
+    {
+        return $this->getCount();
+    }
+
+    /**
+     * Returns the session variable value with the session variable name.
+     * If the session variable does not exist, the `$defaultValue` will be returned.
+     * @param string $key the session variable name
+     * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
+     * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
+     */
+    public function get($key, $defaultValue = null)
+    {
+        $this->open();
+        return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
+    }
+
+    /**
+     * Adds a session variable.
+     * If the specified name already exists, the old value will be overwritten.
+     * @param string $key session variable name
+     * @param mixed $value session variable value
+     */
+    public function set($key, $value)
+    {
+        $this->open();
+        $_SESSION[$key] = $value;
+    }
+
+    /**
+     * Removes a session variable.
+     * @param string $key the name of the session variable to be removed
+     * @return mixed the removed value, null if no such session variable.
+     */
+    public function remove($key)
+    {
+        $this->open();
+        if (isset($_SESSION[$key])) {
+            $value = $_SESSION[$key];
+            unset($_SESSION[$key]);
+
+            return $value;
+        }
+
+        return null;
+    }
+
+    /**
+     * Removes all session variables.
+     */
+    public function removeAll()
+    {
+        $this->open();
+        foreach (array_keys($_SESSION) as $key) {
+            unset($_SESSION[$key]);
+        }
+    }
+
+    /**
+     * @param mixed $key session variable name
+     * @return bool whether there is the named session variable
+     */
+    public function has($key)
+    {
+        $this->open();
+        return isset($_SESSION[$key]);
+    }
+
+    /**
+     * Updates the counters for flash messages and removes outdated flash messages.
+     * This method should only be called once in [[init()]].
+     */
+    protected function updateFlashCounters()
+    {
+        $counters = $this->get($this->flashParam, []);
+        if (is_array($counters)) {
+            foreach ($counters as $key => $count) {
+                if ($count > 0) {
+                    unset($counters[$key], $_SESSION[$key]);
+                } elseif ($count == 0) {
+                    $counters[$key]++;
+                }
+            }
+            $_SESSION[$this->flashParam] = $counters;
+        } else {
+            // fix the unexpected problem that flashParam doesn't return an array
+            unset($_SESSION[$this->flashParam]);
+        }
+    }
+
+    /**
+     * Returns a flash message.
+     * @param string $key the key identifying the flash message
+     * @param mixed $defaultValue value to be returned if the flash message does not exist.
+     * @param bool $delete whether to delete this flash message right after this method is called.
+     * If false, the flash message will be automatically deleted in the next request.
+     * @return mixed the flash message or an array of messages if addFlash was used
+     * @see setFlash()
+     * @see addFlash()
+     * @see hasFlash()
+     * @see getAllFlashes()
+     * @see removeFlash()
+     */
+    public function getFlash($key, $defaultValue = null, $delete = false)
+    {
+        $counters = $this->get($this->flashParam, []);
+        if (isset($counters[$key])) {
+            $value = $this->get($key, $defaultValue);
+            if ($delete) {
+                $this->removeFlash($key);
+            } elseif ($counters[$key] < 0) {
+                // mark for deletion in the next request
+                $counters[$key] = 1;
+                $_SESSION[$this->flashParam] = $counters;
+            }
+
+            return $value;
+        }
+
+        return $defaultValue;
+    }
+
+    /**
+     * Returns all flash messages.
+     *
+     * You may use this method to display all the flash messages in a view file:
+     *
+     * ```php
+     * <?php
+     * foreach (Yii::$app->session->getAllFlashes() as $key => $message) {
+     *     echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
+     * } ?>
+     * ```
+     *
+     * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
+     * as the flash message key to influence the color of the div.
+     *
+     * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code.
+     *
+     * [bootstrap alert]: http://getbootstrap.com/components/#alerts
+     *
+     * @param bool $delete whether to delete the flash messages right after this method is called.
+     * If false, the flash messages will be automatically deleted in the next request.
+     * @return array flash messages (key => message or key => [message1, message2]).
+     * @see setFlash()
+     * @see addFlash()
+     * @see getFlash()
+     * @see hasFlash()
+     * @see removeFlash()
+     */
+    public function getAllFlashes($delete = false)
+    {
+        $counters = $this->get($this->flashParam, []);
+        $flashes = [];
+        foreach (array_keys($counters) as $key) {
+            if (array_key_exists($key, $_SESSION)) {
+                $flashes[$key] = $_SESSION[$key];
+                if ($delete) {
+                    unset($counters[$key], $_SESSION[$key]);
+                } elseif ($counters[$key] < 0) {
+                    // mark for deletion in the next request
+                    $counters[$key] = 1;
+                }
+            } else {
+                unset($counters[$key]);
+            }
+        }
+
+        $_SESSION[$this->flashParam] = $counters;
+
+        return $flashes;
+    }
+
+    /**
+     * Sets a flash message.
+     * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen
+     * in the next request.
+     * If there is already an existing flash message with the same key, it will be overwritten by the new one.
+     * @param string $key the key identifying the flash message. Note that flash messages
+     * and normal session variables share the same name space. If you have a normal
+     * session variable using the same name, its value will be overwritten by this method.
+     * @param mixed $value flash message
+     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
+     * it is accessed. If false, the flash message will be automatically removed after the next request,
+     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
+     * it is accessed.
+     * @see getFlash()
+     * @see addFlash()
+     * @see removeFlash()
+     */
+    public function setFlash($key, $value = true, $removeAfterAccess = true)
+    {
+        $counters = $this->get($this->flashParam, []);
+        $counters[$key] = $removeAfterAccess ? -1 : 0;
+        $_SESSION[$key] = $value;
+        $_SESSION[$this->flashParam] = $counters;
+    }
+
+    /**
+     * Adds a flash message.
+     * If there are existing flash messages with the same key, the new one will be appended to the existing message array.
+     * @param string $key the key identifying the flash message.
+     * @param mixed $value flash message
+     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
+     * it is accessed. If false, the flash message will be automatically removed after the next request,
+     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
+     * it is accessed.
+     * @see getFlash()
+     * @see setFlash()
+     * @see removeFlash()
+     */
+    public function addFlash($key, $value = true, $removeAfterAccess = true)
+    {
+        $counters = $this->get($this->flashParam, []);
+        $counters[$key] = $removeAfterAccess ? -1 : 0;
+        $_SESSION[$this->flashParam] = $counters;
+        if (empty($_SESSION[$key])) {
+            $_SESSION[$key] = [$value];
+        } elseif (is_array($_SESSION[$key])) {
+            $_SESSION[$key][] = $value;
+        } else {
+            $_SESSION[$key] = [$_SESSION[$key], $value];
+        }
+    }
+
+    /**
+     * Removes a flash message.
+     * @param string $key the key identifying the flash message. Note that flash messages
+     * and normal session variables share the same name space.  If you have a normal
+     * session variable using the same name, it will be removed by this method.
+     * @return mixed the removed flash message. Null if the flash message does not exist.
+     * @see getFlash()
+     * @see setFlash()
+     * @see addFlash()
+     * @see removeAllFlashes()
+     */
+    public function removeFlash($key)
+    {
+        $counters = $this->get($this->flashParam, []);
+        $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
+        unset($counters[$key], $_SESSION[$key]);
+        $_SESSION[$this->flashParam] = $counters;
+
+        return $value;
+    }
+
+    /**
+     * Removes all flash messages.
+     * Note that flash messages and normal session variables share the same name space.
+     * If you have a normal session variable using the same name, it will be removed
+     * by this method.
+     * @see getFlash()
+     * @see setFlash()
+     * @see addFlash()
+     * @see removeFlash()
+     */
+    public function removeAllFlashes()
+    {
+        $counters = $this->get($this->flashParam, []);
+        foreach (array_keys($counters) as $key) {
+            unset($_SESSION[$key]);
+        }
+        unset($_SESSION[$this->flashParam]);
+    }
+
+    /**
+     * Returns a value indicating whether there are flash messages associated with the specified key.
+     * @param string $key key identifying the flash message type
+     * @return bool whether any flash messages exist under specified key
+     */
+    public function hasFlash($key)
+    {
+        return $this->getFlash($key) !== null;
+    }
+
+    /**
+     * This method is required by the interface [[\ArrayAccess]].
+     * @param mixed $offset the offset to check on
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        $this->open();
+
+        return isset($_SESSION[$offset]);
+    }
+
+    /**
+     * This method is required by the interface [[\ArrayAccess]].
+     * @param int $offset the offset to retrieve element.
+     * @return mixed the element at the offset, null if no element is found at the offset
+     */
+    public function offsetGet($offset)
+    {
+        $this->open();
+
+        return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
+    }
+
+    /**
+     * This method is required by the interface [[\ArrayAccess]].
+     * @param int $offset the offset to set element
+     * @param mixed $item the element value
+     */
+    public function offsetSet($offset, $item)
+    {
+        $this->open();
+        $_SESSION[$offset] = $item;
+    }
+
+    /**
+     * This method is required by the interface [[\ArrayAccess]].
+     * @param mixed $offset the offset to unset element
+     */
+    public function offsetUnset($offset)
+    {
+        $this->open();
+        unset($_SESSION[$offset]);
+    }
+
+    /**
+     * If session is started it's not possible to edit session ini settings. In PHP7.2+ it throws exception.
+     * This function saves session data to temporary variable and stop session.
+     * @since 2.0.14
+     */
+    protected function freeze()
+    {
+        if ($this->getIsActive()) {
+            if (isset($_SESSION)) {
+                $this->frozenSessionData = $_SESSION;
+            }
+            $this->close();
+            Yii::info('Session frozen', __METHOD__);
+        }
+    }
+
+    /**
+     * Starts session and restores data from temporary variable
+     * @since 2.0.14
+     */
+    protected function unfreeze()
+    {
+        if (null !== $this->frozenSessionData) {
+
+            YII_DEBUG ? session_start() : @session_start();
+
+            if ($this->getIsActive()) {
+                Yii::info('Session unfrozen', __METHOD__);
+            } else {
+                $error = error_get_last();
+                $message = isset($error['message']) ? $error['message'] : 'Failed to unfreeze session.';
+                Yii::error($message, __METHOD__);
+            }
+
+            $_SESSION = $this->frozenSessionData;
+            $this->frozenSessionData = null;
+        }
+    }
+
+    /**
+     * Set cache limiter
+     *
+     * @param string $cacheLimiter
+     * @since 2.0.14
+     */
+    public function setCacheLimiter($cacheLimiter)
+    {
+        $this->freeze();
+        session_cache_limiter($cacheLimiter);
+        $this->unfreeze();
+    }
+
+    /**
+     * Returns current cache limiter
+     *
+     * @return string current cache limiter
+     * @since 2.0.14
+     */
+    public function getCacheLimiter()
+    {
+        return session_cache_limiter();
+    }
+}

+ 85 - 0
vendor/yiisoft/yii2/web/SessionIterator.php

@@ -0,0 +1,85 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * SessionIterator implements an [[\Iterator|iterator]] for traversing session variables managed by [[Session]].
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class SessionIterator implements \Iterator
+{
+    /**
+     * @var array list of keys in the map
+     */
+    private $_keys;
+    /**
+     * @var mixed current key
+     */
+    private $_key;
+
+
+    /**
+     * Constructor.
+     */
+    public function __construct()
+    {
+        $this->_keys = array_keys($_SESSION);
+    }
+
+    /**
+     * Rewinds internal array pointer.
+     * This method is required by the interface [[\Iterator]].
+     */
+    public function rewind()
+    {
+        $this->_key = reset($this->_keys);
+    }
+
+    /**
+     * Returns the key of the current array element.
+     * This method is required by the interface [[\Iterator]].
+     * @return mixed the key of the current array element
+     */
+    public function key()
+    {
+        return $this->_key;
+    }
+
+    /**
+     * Returns the current array element.
+     * This method is required by the interface [[\Iterator]].
+     * @return mixed the current array element
+     */
+    public function current()
+    {
+        return isset($_SESSION[$this->_key]) ? $_SESSION[$this->_key] : null;
+    }
+
+    /**
+     * Moves the internal pointer to the next array element.
+     * This method is required by the interface [[\Iterator]].
+     */
+    public function next()
+    {
+        do {
+            $this->_key = next($this->_keys);
+        } while (!isset($_SESSION[$this->_key]) && $this->_key !== false);
+    }
+
+    /**
+     * Returns whether there is an element at current position.
+     * This method is required by the interface [[\Iterator]].
+     * @return bool
+     */
+    public function valid()
+    {
+        return $this->_key !== false;
+    }
+}

+ 33 - 0
vendor/yiisoft/yii2/web/TooManyRequestsHttpException.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * TooManyRequestsHttpException represents a "Too Many Requests" HTTP exception with status code 429.
+ *
+ * Use this exception to indicate that a client has made too many requests in a
+ * given period of time. For example, you would throw this exception when
+ * 'throttling' an API user.
+ *
+ * @see https://tools.ietf.org/html/rfc6585#section-4
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class TooManyRequestsHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(429, $message, $code, $previous);
+    }
+}

+ 36 - 0
vendor/yiisoft/yii2/web/UnauthorizedHttpException.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * UnauthorizedHttpException represents an "Unauthorized" HTTP exception with status code 401.
+ *
+ * Use this exception to indicate that a client needs to authenticate via WWW-Authenticate header
+ * to perform the requested action.
+ *
+ * If the client is already authenticated and is simply not allowed to
+ * perform the action, consider using a 403 [[ForbiddenHttpException]]
+ * or 404 [[NotFoundHttpException]] instead.
+ *
+ * @link https://tools.ietf.org/html/rfc7235#section-3.1
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class UnauthorizedHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(401, $message, $code, $previous);
+    }
+}

+ 35 - 0
vendor/yiisoft/yii2/web/UnprocessableEntityHttpException.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * UnprocessableEntityHttpException represents an "Unprocessable Entity" HTTP
+ * exception with status code 422.
+ *
+ * Use this exception to inform that the server understands the content type of
+ * the request entity and the syntax of that request entity is correct but the server
+ * was unable to process the contained instructions. For example, to return form
+ * validation errors.
+ *
+ * @link http://www.webdav.org/specs/rfc2518.html#STATUS_422
+ * @author Jan Silva <janfrs3@gmail.com>
+ * @since 2.0.7
+ */
+class UnprocessableEntityHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(422, $message, $code, $previous);
+    }
+}

+ 34 - 0
vendor/yiisoft/yii2/web/UnsupportedMediaTypeHttpException.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * UnsupportedMediaTypeHttpException represents an "Unsupported Media Type" HTTP exception with status code 415.
+ *
+ * Use this exception when the client sends data in a format that your
+ * application does not understand. For example, you would throw this exception
+ * if the client POSTs XML data to an action or controller that only accepts
+ * JSON.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-6.5.13
+ * @author Dan Schmidt <danschmidt5189@gmail.com>
+ * @since 2.0
+ */
+class UnsupportedMediaTypeHttpException extends HttpException
+{
+    /**
+     * Constructor.
+     * @param string $message error message
+     * @param int $code error code
+     * @param \Exception $previous The previous exception used for the exception chaining.
+     */
+    public function __construct($message = null, $code = 0, \Exception $previous = null)
+    {
+        parent::__construct(415, $message, $code, $previous);
+    }
+}

+ 284 - 0
vendor/yiisoft/yii2/web/UploadedFile.php

@@ -0,0 +1,284 @@
+<?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\helpers\ArrayHelper;
+use yii\helpers\Html;
+
+/**
+ * UploadedFile represents the information for an uploaded file.
+ *
+ * You can call [[getInstance()]] to retrieve the instance of an uploaded file,
+ * and then use [[saveAs()]] to save it on the server.
+ * You may also query other information about the file, including [[name]],
+ * [[tempName]], [[type]], [[size]] and [[error]].
+ *
+ * For more details and usage information on UploadedFile, see the [guide article on handling uploads](guide:input-file-upload).
+ *
+ * @property-read string $baseName Original file base name. This property is read-only.
+ * @property-read string $extension File extension. This property is read-only.
+ * @property-read bool $hasError Whether there is an error with the uploaded file. Check [[error]] for
+ * detailed error code information. This property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class UploadedFile extends BaseObject
+{
+    /**
+     * @var string the original name of the file being uploaded
+     */
+    public $name;
+    /**
+     * @var string the path of the uploaded file on the server.
+     * Note, this is a temporary file which will be automatically deleted by PHP
+     * after the current request is processed.
+     */
+    public $tempName;
+    /**
+     * @var string the MIME-type of the uploaded file (such as "image/gif").
+     * Since this MIME type is not checked on the server-side, do not take this value for granted.
+     * Instead, use [[\yii\helpers\FileHelper::getMimeType()]] to determine the exact MIME type.
+     */
+    public $type;
+    /**
+     * @var int the actual size of the uploaded file in bytes
+     */
+    public $size;
+    /**
+     * @var int an error code describing the status of this file uploading.
+     * @see https://secure.php.net/manual/en/features.file-upload.errors.php
+     */
+    public $error;
+
+    /**
+     * @var resource a temporary uploaded stream resource used within PUT and PATCH request.
+     */
+    private $_tempResource;
+    private static $_files;
+
+
+    /**
+     * UploadedFile constructor.
+     *
+     * @param array $config name-value pairs that will be used to initialize the object properties
+     */
+    public function __construct($config = [])
+    {
+        $this->_tempResource = ArrayHelper::remove($config, 'tempResource');
+        parent::__construct($config);
+    }
+
+    /**
+     * String output.
+     * This is PHP magic method that returns string representation of an object.
+     * The implementation here returns the uploaded file's name.
+     * @return string the string representation of the object
+     */
+    public function __toString()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Returns an uploaded file for the given model attribute.
+     * The file should be uploaded using [[\yii\widgets\ActiveField::fileInput()]].
+     * @param \yii\base\Model $model the data model
+     * @param string $attribute the attribute name. The attribute name may contain array indexes.
+     * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
+     * @return null|UploadedFile the instance of the uploaded file.
+     * Null is returned if no file is uploaded for the specified model attribute.
+     * @see getInstanceByName()
+     */
+    public static function getInstance($model, $attribute)
+    {
+        $name = Html::getInputName($model, $attribute);
+        return static::getInstanceByName($name);
+    }
+
+    /**
+     * Returns all uploaded files for the given model attribute.
+     * @param \yii\base\Model $model the data model
+     * @param string $attribute the attribute name. The attribute name may contain array indexes
+     * for tabular file uploading, e.g. '[1]file'.
+     * @return UploadedFile[] array of UploadedFile objects.
+     * Empty array is returned if no available file was found for the given attribute.
+     */
+    public static function getInstances($model, $attribute)
+    {
+        $name = Html::getInputName($model, $attribute);
+        return static::getInstancesByName($name);
+    }
+
+    /**
+     * Returns an uploaded file according to the given file input name.
+     * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
+     * @param string $name the name of the file input field.
+     * @return null|UploadedFile the instance of the uploaded file.
+     * Null is returned if no file is uploaded for the specified name.
+     */
+    public static function getInstanceByName($name)
+    {
+        $files = self::loadFiles();
+        return isset($files[$name]) ? new static($files[$name]) : null;
+    }
+
+    /**
+     * Returns an array of uploaded files corresponding to the specified file input name.
+     * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
+     * 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
+     * @param string $name the name of the array of files
+     * @return UploadedFile[] the array of UploadedFile objects. Empty array is returned
+     * if no adequate upload was found. Please note that this array will contain
+     * all files from all sub-arrays regardless how deeply nested they are.
+     */
+    public static function getInstancesByName($name)
+    {
+        $files = self::loadFiles();
+        if (isset($files[$name])) {
+            return [new static($files[$name])];
+        }
+        $results = [];
+        foreach ($files as $key => $file) {
+            if (strpos($key, "{$name}[") === 0) {
+                $results[] = new static($file);
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * Cleans up the loaded UploadedFile instances.
+     * This method is mainly used by test scripts to set up a fixture.
+     */
+    public static function reset()
+    {
+        self::$_files = null;
+    }
+
+    /**
+     * Saves the uploaded file.
+     * If the target file `$file` already exists, it will be overwritten.
+     * @param string $file the file path or a path alias used to save the uploaded file.
+     * @param bool $deleteTempFile whether to delete the temporary file after saving.
+     * If true, you will not be able to save the uploaded file again in the current request.
+     * @return bool true whether the file is saved successfully
+     * @see error
+     */
+    public function saveAs($file, $deleteTempFile = true)
+    {
+        if ($this->hasError) {
+            return false;
+        }
+
+        $targetFile = Yii::getAlias($file);
+        if (is_resource($this->_tempResource)) {
+            $result = $this->copyTempFile($targetFile);
+            return $deleteTempFile ? @fclose($this->_tempResource) : (bool) $result;
+        }
+
+        return $deleteTempFile ? move_uploaded_file($this->tempName, $targetFile) : copy($this->tempName, $targetFile);
+    }
+
+    /**
+     * Copy temporary file into file specified
+     *
+     * @param string $targetFile path of the file to copy to
+     * @return bool|int the total count of bytes copied, or false on failure
+     * @since 2.0.32
+     */
+    protected function copyTempFile($targetFile)
+    {
+        $target = fopen($targetFile, 'wb');
+        if ($target === false) {
+            return false;
+        }
+
+        $result = stream_copy_to_stream($this->_tempResource, $target);
+        @fclose($target);
+
+        return $result;
+    }
+
+    /**
+     * @return string original file base name
+     */
+    public function getBaseName()
+    {
+        // https://github.com/yiisoft/yii2/issues/11012
+        $pathInfo = pathinfo('_' . $this->name, PATHINFO_FILENAME);
+        return mb_substr($pathInfo, 1, mb_strlen($pathInfo, '8bit'), '8bit');
+    }
+
+    /**
+     * @return string file extension
+     */
+    public function getExtension()
+    {
+        return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
+    }
+
+    /**
+     * @return bool whether there is an error with the uploaded file.
+     * Check [[error]] for detailed error code information.
+     */
+    public function getHasError()
+    {
+        return $this->error != UPLOAD_ERR_OK;
+    }
+
+    /**
+     * Creates UploadedFile instances from $_FILE.
+     * @return array the UploadedFile instances
+     */
+    private static function loadFiles()
+    {
+        if (self::$_files === null) {
+            self::$_files = [];
+            if (isset($_FILES) && is_array($_FILES)) {
+                foreach ($_FILES as $class => $info) {
+                    $resource = isset($info['tmp_resource']) ? $info['tmp_resource'] : [];
+                    self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error'], $resource);
+                }
+            }
+        }
+
+        return self::$_files;
+    }
+
+    /**
+     * Creates UploadedFile instances from $_FILE recursively.
+     * @param string $key key for identifying uploaded file: class name and sub-array indexes
+     * @param mixed $names file names provided by PHP
+     * @param mixed $tempNames temporary file names provided by PHP
+     * @param mixed $types file types provided by PHP
+     * @param mixed $sizes file sizes provided by PHP
+     * @param mixed $errors uploading issues provided by PHP
+     */
+    private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors, $tempResources)
+    {
+        if (is_array($names)) {
+            foreach ($names as $i => $name) {
+                $resource = isset($tempResources[$i]) ? $tempResources[$i] : [];
+                self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i], $resource);
+            }
+        } elseif ((int) $errors !== UPLOAD_ERR_NO_FILE) {
+            self::$_files[$key] = [
+                'name' => $names,
+                'tempName' => $tempNames,
+                'tempResource' => $tempResources,
+                'type' => $types,
+                'size' => $sizes,
+                'error' => $errors,
+            ];
+        }
+    }
+}

+ 663 - 0
vendor/yiisoft/yii2/web/UrlManager.php

@@ -0,0 +1,663 @@
+<?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\Component;
+use yii\base\InvalidConfigException;
+use yii\caching\CacheInterface;
+use yii\di\Instance;
+use yii\helpers\Url;
+
+/**
+ * UrlManager handles HTTP request parsing and creation of URLs based on a set of rules.
+ *
+ * UrlManager is configured as an application component in [[\yii\base\Application]] by default.
+ * You can access that instance via `Yii::$app->urlManager`.
+ *
+ * You can modify its configuration by adding an array to your application config under `components`
+ * as it is shown in the following example:
+ *
+ * ```php
+ * 'urlManager' => [
+ *     'enablePrettyUrl' => true,
+ *     'rules' => [
+ *         // your rules go here
+ *     ],
+ *     // ...
+ * ]
+ * ```
+ *
+ * Rules are classes implementing the [[UrlRuleInterface]], by default that is [[UrlRule]].
+ * For nesting rules, there is also a [[GroupUrlRule]] class.
+ *
+ * For more details and usage information on UrlManager, see the [guide article on routing](guide:runtime-routing).
+ *
+ * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend to created URLs.
+ * @property string $hostInfo The host info (e.g. `http://www.example.com`) that is used by
+ * [[createAbsoluteUrl()]] to prepend to created URLs.
+ * @property string $scriptUrl The entry script URL that is used by [[createUrl()]] to prepend to created
+ * URLs.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class UrlManager extends Component
+{
+    /**
+     * @var bool whether to enable pretty URLs. Instead of putting all parameters in the query
+     * string part of a URL, pretty URLs allow using path info to represent some of the parameters
+     * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of
+     * "/index.php?r=news%2Fview&id=100".
+     */
+    public $enablePrettyUrl = false;
+    /**
+     * @var bool whether to enable strict parsing. If strict parsing is enabled, the incoming
+     * requested URL must match at least one of the [[rules]] in order to be treated as a valid request.
+     * Otherwise, the path info part of the request will be treated as the requested route.
+     * This property is used only when [[enablePrettyUrl]] is `true`.
+     */
+    public $enableStrictParsing = false;
+    /**
+     * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is `true`.
+     * This property is used only if [[enablePrettyUrl]] is `true`. Each element in the array
+     * is the configuration array for creating a single URL rule. The configuration will
+     * be merged with [[ruleConfig]] first before it is used for creating the rule object.
+     *
+     * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
+     * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
+     * array, one can use the key to represent the pattern and the value the corresponding route.
+     * For example, `'post/<id:\d+>' => 'post/view'`.
+     *
+     * For RESTful routing the mentioned shortcut format also allows you to specify the
+     * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
+     * You can do that  by prepending it to the pattern, separated by space.
+     * For example, `'PUT post/<id:\d+>' => 'post/update'`.
+     * You may specify multiple verbs by separating them with comma
+     * like this: `'POST,PUT post/index' => 'post/create'`.
+     * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE.
+     * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
+     * so you normally would not specify a verb for normal GET request.
+     *
+     * Here is an example configuration for RESTful CRUD controller:
+     *
+     * ```php
+     * [
+     *     'dashboard' => 'site/index',
+     *
+     *     'POST <controller:[\w-]+>' => '<controller>/create',
+     *     '<controller:[\w-]+>s' => '<controller>/index',
+     *
+     *     'PUT <controller:[\w-]+>/<id:\d+>'    => '<controller>/update',
+     *     'DELETE <controller:[\w-]+>/<id:\d+>' => '<controller>/delete',
+     *     '<controller:[\w-]+>/<id:\d+>'        => '<controller>/view',
+     * ];
+     * ```
+     *
+     * Note that if you modify this property after the UrlManager object is created, make sure
+     * you populate the array with rule objects instead of rule configurations.
+     */
+    public $rules = [];
+    /**
+     * @var string the URL suffix used when [[enablePrettyUrl]] is `true`.
+     * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
+     * This property is used only if [[enablePrettyUrl]] is `true`.
+     */
+    public $suffix;
+    /**
+     * @var bool whether to show entry script name in the constructed URL. Defaults to `true`.
+     * This property is used only if [[enablePrettyUrl]] is `true`.
+     */
+    public $showScriptName = true;
+    /**
+     * @var string the GET parameter name for route. This property is used only if [[enablePrettyUrl]] is `false`.
+     */
+    public $routeParam = 'r';
+    /**
+     * @var CacheInterface|array|string|bool the cache object or the application component ID of the cache object.
+     * This can also be an array that is used to create a [[CacheInterface]] instance in case you do not want to use
+     * an application component.
+     * Compiled URL rules will be cached through this cache object, if it is available.
+     *
+     * After the UrlManager object is created, if you want to change this property,
+     * you should only assign it with a cache object.
+     * Set this property to `false` or `null` if you do not want to cache the URL rules.
+     *
+     * Cache entries are stored for the time set by [[\yii\caching\Cache::$defaultDuration|$defaultDuration]] in
+     * the cache configuration, which is unlimited by default. You may want to tune this value if your [[rules]]
+     * change frequently.
+     */
+    public $cache = 'cache';
+    /**
+     * @var array the default configuration of URL rules. Individual rule configurations
+     * specified via [[rules]] will take precedence when the same property of the rule is configured.
+     */
+    public $ruleConfig = ['class' => 'yii\web\UrlRule'];
+    /**
+     * @var UrlNormalizer|array|string|false the configuration for [[UrlNormalizer]] used by this UrlManager.
+     * The default value is `false`, which means normalization will be skipped.
+     * If you wish to enable URL normalization, you should configure this property manually.
+     * For example:
+     *
+     * ```php
+     * [
+     *     'class' => 'yii\web\UrlNormalizer',
+     *     'collapseSlashes' => true,
+     *     'normalizeTrailingSlash' => true,
+     * ]
+     * ```
+     *
+     * @since 2.0.10
+     */
+    public $normalizer = false;
+
+    /**
+     * @var string the cache key for cached rules
+     * @since 2.0.8
+     */
+    protected $cacheKey = __CLASS__;
+
+    private $_baseUrl;
+    private $_scriptUrl;
+    private $_hostInfo;
+    private $_ruleCache;
+
+
+    /**
+     * Initializes UrlManager.
+     */
+    public function init()
+    {
+        parent::init();
+
+        if ($this->normalizer !== false) {
+            $this->normalizer = Yii::createObject($this->normalizer);
+            if (!$this->normalizer instanceof UrlNormalizer) {
+                throw new InvalidConfigException('`' . get_class($this) . '::normalizer` should be an instance of `' . UrlNormalizer::className() . '` or its DI compatible configuration.');
+            }
+        }
+
+        if (!$this->enablePrettyUrl) {
+            return;
+        }
+
+        if (!empty($this->rules)) {
+            $this->rules = $this->buildRules($this->rules);
+        }
+    }
+
+    /**
+     * Adds additional URL rules.
+     *
+     * This method will call [[buildRules()]] to parse the given rule declarations and then append or insert
+     * them to the existing [[rules]].
+     *
+     * Note that if [[enablePrettyUrl]] is `false`, this method will do nothing.
+     *
+     * @param array $rules the new rules to be added. Each array element represents a single rule declaration.
+     * Please refer to [[rules]] for the acceptable rule format.
+     * @param bool $append whether to add the new rules by appending them to the end of the existing rules.
+     */
+    public function addRules($rules, $append = true)
+    {
+        if (!$this->enablePrettyUrl) {
+            return;
+        }
+        $rules = $this->buildRules($rules);
+        if ($append) {
+            $this->rules = array_merge($this->rules, $rules);
+        } else {
+            $this->rules = array_merge($rules, $this->rules);
+        }
+    }
+
+    /**
+     * Builds URL rule objects from the given rule declarations.
+     *
+     * @param array $ruleDeclarations the rule declarations. Each array element represents a single rule declaration.
+     * Please refer to [[rules]] for the acceptable rule formats.
+     * @return UrlRuleInterface[] the rule objects built from the given rule declarations
+     * @throws InvalidConfigException if a rule declaration is invalid
+     */
+    protected function buildRules($ruleDeclarations)
+    {
+        $builtRules = $this->getBuiltRulesFromCache($ruleDeclarations);
+        if ($builtRules !== false) {
+            return $builtRules;
+        }
+
+        $builtRules = [];
+        $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
+        foreach ($ruleDeclarations as $key => $rule) {
+            if (is_string($rule)) {
+                $rule = ['route' => $rule];
+                if (preg_match("/^((?:($verbs),)*($verbs))\\s+(.*)$/", $key, $matches)) {
+                    $rule['verb'] = explode(',', $matches[1]);
+                    $key = $matches[4];
+                }
+                $rule['pattern'] = $key;
+            }
+            if (is_array($rule)) {
+                $rule = Yii::createObject(array_merge($this->ruleConfig, $rule));
+            }
+            if (!$rule instanceof UrlRuleInterface) {
+                throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.');
+            }
+            $builtRules[] = $rule;
+        }
+
+        $this->setBuiltRulesCache($ruleDeclarations, $builtRules);
+
+        return $builtRules;
+    }
+
+    /**
+     * @return CacheInterface|null|bool
+     */
+    private function ensureCache()
+    {
+        if (!$this->cache instanceof CacheInterface && $this->cache !== false && $this->cache !== null) {
+            try {
+                $this->cache = Instance::ensure($this->cache, 'yii\caching\CacheInterface');
+            } catch (InvalidConfigException $e) {
+                Yii::warning('Unable to use cache for URL manager: ' . $e->getMessage());
+                $this->cache = null;
+            }
+        }
+
+        return $this->cache;
+    }
+
+    /**
+     * Stores $builtRules to cache, using $rulesDeclaration as a part of cache key.
+     *
+     * @param array $ruleDeclarations the rule declarations. Each array element represents a single rule declaration.
+     * Please refer to [[rules]] for the acceptable rule formats.
+     * @param UrlRuleInterface[] $builtRules the rule objects built from the given rule declarations.
+     * @return bool whether the value is successfully stored into cache
+     * @since 2.0.14
+     */
+    protected function setBuiltRulesCache($ruleDeclarations, $builtRules)
+    {
+        $cache = $this->ensureCache();
+        if (!$cache) {
+            return false;
+        }
+
+        return $cache->set([$this->cacheKey, $this->ruleConfig, $ruleDeclarations], $builtRules);
+    }
+
+    /**
+     * Provides the built URL rules that are associated with the $ruleDeclarations from cache.
+     *
+     * @param array $ruleDeclarations the rule declarations. Each array element represents a single rule declaration.
+     * Please refer to [[rules]] for the acceptable rule formats.
+     * @return UrlRuleInterface[]|false the rule objects built from the given rule declarations or boolean `false` when
+     * there are no cache items for this definition exists.
+     * @since 2.0.14
+     */
+    protected function getBuiltRulesFromCache($ruleDeclarations)
+    {
+        $cache = $this->ensureCache();
+        if (!$cache) {
+            return false;
+        }
+
+        return $cache->get([$this->cacheKey, $this->ruleConfig, $ruleDeclarations]);
+    }
+
+    /**
+     * Parses the user request.
+     * @param Request $request the request component
+     * @return array|bool the route and the associated parameters. The latter is always empty
+     * if [[enablePrettyUrl]] is `false`. `false` is returned if the current request cannot be successfully parsed.
+     */
+    public function parseRequest($request)
+    {
+        if ($this->enablePrettyUrl) {
+            /* @var $rule UrlRule */
+            foreach ($this->rules as $rule) {
+                $result = $rule->parseRequest($this, $request);
+                if (YII_DEBUG) {
+                    Yii::debug([
+                        'rule' => method_exists($rule, '__toString') ? $rule->__toString() : get_class($rule),
+                        'match' => $result !== false,
+                        'parent' => null,
+                    ], __METHOD__);
+                }
+                if ($result !== false) {
+                    return $result;
+                }
+            }
+
+            if ($this->enableStrictParsing) {
+                return false;
+            }
+
+            Yii::debug('No matching URL rules. Using default URL parsing logic.', __METHOD__);
+
+            $suffix = (string) $this->suffix;
+            $pathInfo = $request->getPathInfo();
+            $normalized = false;
+            if ($this->normalizer !== false) {
+                $pathInfo = $this->normalizer->normalizePathInfo($pathInfo, $suffix, $normalized);
+            }
+            if ($suffix !== '' && $pathInfo !== '') {
+                $n = strlen($this->suffix);
+                if (substr_compare($pathInfo, $this->suffix, -$n, $n) === 0) {
+                    $pathInfo = substr($pathInfo, 0, -$n);
+                    if ($pathInfo === '') {
+                        // suffix alone is not allowed
+                        return false;
+                    }
+                } else {
+                    // suffix doesn't match
+                    return false;
+                }
+            }
+
+            if ($normalized) {
+                // pathInfo was changed by normalizer - we need also normalize route
+                return $this->normalizer->normalizeRoute([$pathInfo, []]);
+            }
+
+            return [$pathInfo, []];
+        }
+
+        Yii::debug('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
+        $route = $request->getQueryParam($this->routeParam, '');
+        if (is_array($route)) {
+            $route = '';
+        }
+
+        return [(string) $route, []];
+    }
+
+    /**
+     * Creates a URL using the given route and query parameters.
+     *
+     * You may specify the route as a string, e.g., `site/index`. You may also use an array
+     * if you want to specify additional query parameters for the URL being created. The
+     * array format must be:
+     *
+     * ```php
+     * // generates: /index.php?r=site%2Findex&param1=value1&param2=value2
+     * ['site/index', 'param1' => 'value1', 'param2' => 'value2']
+     * ```
+     *
+     * If you want to create a URL with an anchor, you can use the array format with a `#` parameter.
+     * For example,
+     *
+     * ```php
+     * // generates: /index.php?r=site%2Findex&param1=value1#name
+     * ['site/index', 'param1' => 'value1', '#' => 'name']
+     * ```
+     *
+     * The URL created is a relative one. Use [[createAbsoluteUrl()]] to create an absolute URL.
+     *
+     * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route
+     * as an absolute route.
+     *
+     * @param string|array $params use a string to represent a route (e.g. `site/index`),
+     * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`).
+     * @return string the created URL
+     */
+    public function createUrl($params)
+    {
+        $params = (array) $params;
+        $anchor = isset($params['#']) ? '#' . $params['#'] : '';
+        unset($params['#'], $params[$this->routeParam]);
+
+        $route = trim($params[0], '/');
+        unset($params[0]);
+
+        $baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl();
+
+        if ($this->enablePrettyUrl) {
+            $cacheKey = $route . '?';
+            foreach ($params as $key => $value) {
+                if ($value !== null) {
+                    $cacheKey .= $key . '&';
+                }
+            }
+
+            $url = $this->getUrlFromCache($cacheKey, $route, $params);
+            if ($url === false) {
+                /* @var $rule UrlRule */
+                foreach ($this->rules as $rule) {
+                    if (in_array($rule, $this->_ruleCache[$cacheKey], true)) {
+                        // avoid redundant calls of `UrlRule::createUrl()` for rules checked in `getUrlFromCache()`
+                        // @see https://github.com/yiisoft/yii2/issues/14094
+                        continue;
+                    }
+                    $url = $rule->createUrl($this, $route, $params);
+                    if ($this->canBeCached($rule)) {
+                        $this->setRuleToCache($cacheKey, $rule);
+                    }
+                    if ($url !== false) {
+                        break;
+                    }
+                }
+            }
+
+            if ($url !== false) {
+                if (strpos($url, '://') !== false) {
+                    if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
+                        return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor;
+                    }
+
+                    return $url . $baseUrl . $anchor;
+                } elseif (strncmp($url, '//', 2) === 0) {
+                    if ($baseUrl !== '' && ($pos = strpos($url, '/', 2)) !== false) {
+                        return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor;
+                    }
+
+                    return $url . $baseUrl . $anchor;
+                }
+
+                $url = ltrim($url, '/');
+                return "$baseUrl/{$url}{$anchor}";
+            }
+
+            if ($this->suffix !== null) {
+                $route .= $this->suffix;
+            }
+            if (!empty($params) && ($query = http_build_query($params)) !== '') {
+                $route .= '?' . $query;
+            }
+
+            $route = ltrim($route, '/');
+            return "$baseUrl/{$route}{$anchor}";
+        }
+
+        $url = "$baseUrl?{$this->routeParam}=" . urlencode($route);
+        if (!empty($params) && ($query = http_build_query($params)) !== '') {
+            $url .= '&' . $query;
+        }
+
+        return $url . $anchor;
+    }
+
+    /**
+     * Returns the value indicating whether result of [[createUrl()]] of rule should be cached in internal cache.
+     *
+     * @param UrlRuleInterface $rule
+     * @return bool `true` if result should be cached, `false` if not.
+     * @since 2.0.12
+     * @see getUrlFromCache()
+     * @see setRuleToCache()
+     * @see UrlRule::getCreateUrlStatus()
+     */
+    protected function canBeCached(UrlRuleInterface $rule)
+    {
+        return
+            // if rule does not provide info about create status, we cache it every time to prevent bugs like #13350
+            // @see https://github.com/yiisoft/yii2/pull/13350#discussion_r114873476
+            !method_exists($rule, 'getCreateUrlStatus') || ($status = $rule->getCreateUrlStatus()) === null
+            || $status === UrlRule::CREATE_STATUS_SUCCESS
+            || $status & UrlRule::CREATE_STATUS_PARAMS_MISMATCH;
+    }
+
+    /**
+     * Get URL from internal cache if exists.
+     * @param string $cacheKey generated cache key to store data.
+     * @param string $route the route (e.g. `site/index`).
+     * @param array $params rule params.
+     * @return bool|string the created URL
+     * @see createUrl()
+     * @since 2.0.8
+     */
+    protected function getUrlFromCache($cacheKey, $route, $params)
+    {
+        if (!empty($this->_ruleCache[$cacheKey])) {
+            foreach ($this->_ruleCache[$cacheKey] as $rule) {
+                /* @var $rule UrlRule */
+                if (($url = $rule->createUrl($this, $route, $params)) !== false) {
+                    return $url;
+                }
+            }
+        } else {
+            $this->_ruleCache[$cacheKey] = [];
+        }
+
+        return false;
+    }
+
+    /**
+     * Store rule (e.g. [[UrlRule]]) to internal cache.
+     * @param $cacheKey
+     * @param UrlRuleInterface $rule
+     * @since 2.0.8
+     */
+    protected function setRuleToCache($cacheKey, UrlRuleInterface $rule)
+    {
+        $this->_ruleCache[$cacheKey][] = $rule;
+    }
+
+    /**
+     * Creates an absolute URL using the given route and query parameters.
+     *
+     * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]].
+     *
+     * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route
+     * as an absolute route.
+     *
+     * @param string|array $params use a string to represent a route (e.g. `site/index`),
+     * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`).
+     * @param string|null $scheme the scheme to use for the URL (either `http`, `https` or empty string
+     * for protocol-relative URL).
+     * If not specified the scheme of the current request will be used.
+     * @return string the created URL
+     * @see createUrl()
+     */
+    public function createAbsoluteUrl($params, $scheme = null)
+    {
+        $params = (array) $params;
+        $url = $this->createUrl($params);
+        if (strpos($url, '://') === false) {
+            $hostInfo = $this->getHostInfo();
+            if (strncmp($url, '//', 2) === 0) {
+                $url = substr($hostInfo, 0, strpos($hostInfo, '://')) . ':' . $url;
+            } else {
+                $url = $hostInfo . $url;
+            }
+        }
+
+        return Url::ensureScheme($url, $scheme);
+    }
+
+    /**
+     * Returns the base URL that is used by [[createUrl()]] to prepend to created URLs.
+     * It defaults to [[Request::baseUrl]].
+     * This is mainly used when [[enablePrettyUrl]] is `true` and [[showScriptName]] is `false`.
+     * @return string the base URL that is used by [[createUrl()]] to prepend to created URLs.
+     * @throws InvalidConfigException if running in console application and [[baseUrl]] is not configured.
+     */
+    public function getBaseUrl()
+    {
+        if ($this->_baseUrl === null) {
+            $request = Yii::$app->getRequest();
+            if ($request instanceof Request) {
+                $this->_baseUrl = $request->getBaseUrl();
+            } else {
+                throw new InvalidConfigException('Please configure UrlManager::baseUrl correctly as you are running a console application.');
+            }
+        }
+
+        return $this->_baseUrl;
+    }
+
+    /**
+     * Sets the base URL that is used by [[createUrl()]] to prepend to created URLs.
+     * This is mainly used when [[enablePrettyUrl]] is `true` and [[showScriptName]] is `false`.
+     * @param string $value the base URL that is used by [[createUrl()]] to prepend to created URLs.
+     */
+    public function setBaseUrl($value)
+    {
+        $this->_baseUrl = $value === null ? null : rtrim(Yii::getAlias($value), '/');
+    }
+
+    /**
+     * Returns the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
+     * It defaults to [[Request::scriptUrl]].
+     * This is mainly used when [[enablePrettyUrl]] is `false` or [[showScriptName]] is `true`.
+     * @return string the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
+     * @throws InvalidConfigException if running in console application and [[scriptUrl]] is not configured.
+     */
+    public function getScriptUrl()
+    {
+        if ($this->_scriptUrl === null) {
+            $request = Yii::$app->getRequest();
+            if ($request instanceof Request) {
+                $this->_scriptUrl = $request->getScriptUrl();
+            } else {
+                throw new InvalidConfigException('Please configure UrlManager::scriptUrl correctly as you are running a console application.');
+            }
+        }
+
+        return $this->_scriptUrl;
+    }
+
+    /**
+     * Sets the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
+     * This is mainly used when [[enablePrettyUrl]] is `false` or [[showScriptName]] is `true`.
+     * @param string $value the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
+     */
+    public function setScriptUrl($value)
+    {
+        $this->_scriptUrl = $value;
+    }
+
+    /**
+     * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
+     * @return string the host info (e.g. `http://www.example.com`) that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
+     * @throws InvalidConfigException if running in console application and [[hostInfo]] is not configured.
+     */
+    public function getHostInfo()
+    {
+        if ($this->_hostInfo === null) {
+            $request = Yii::$app->getRequest();
+            if ($request instanceof \yii\web\Request) {
+                $this->_hostInfo = $request->getHostInfo();
+            } else {
+                throw new InvalidConfigException('Please configure UrlManager::hostInfo correctly as you are running a console application.');
+            }
+        }
+
+        return $this->_hostInfo;
+    }
+
+    /**
+     * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
+     * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
+     */
+    public function setHostInfo($value)
+    {
+        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
+    }
+}

+ 150 - 0
vendor/yiisoft/yii2/web/UrlNormalizer.php

@@ -0,0 +1,150 @@
+<?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;
+
+/**
+ * UrlNormalizer normalizes URLs for [[UrlManager]] and [[UrlRule]].
+ *
+ * @author Robert Korulczyk <robert@korulczyk.pl>
+ * @author Cronfy <cronfy@gmail.com>
+ * @since 2.0.10
+ */
+class UrlNormalizer extends BaseObject
+{
+    /**
+     * Represents permament redirection during route normalization.
+     * @see https://en.wikipedia.org/wiki/HTTP_301
+     */
+    const ACTION_REDIRECT_PERMANENT = 301;
+    /**
+     * Represents temporary redirection during route normalization.
+     * @see https://en.wikipedia.org/wiki/HTTP_302
+     */
+    const ACTION_REDIRECT_TEMPORARY = 302;
+    /**
+     * Represents showing 404 error page during route normalization.
+     * @see https://en.wikipedia.org/wiki/HTTP_404
+     */
+    const ACTION_NOT_FOUND = 404;
+
+    /**
+     * @var bool whether slashes should be collapsed, for example `site///index` will be
+     * converted into `site/index`
+     */
+    public $collapseSlashes = true;
+    /**
+     * @var bool whether trailing slash should be normalized according to the suffix settings
+     * of the rule
+     */
+    public $normalizeTrailingSlash = true;
+    /**
+     * @var int|callable|null action to perform during route normalization.
+     * Available options are:
+     * - `null` - no special action will be performed
+     * - `301` - the request should be redirected to the normalized URL using
+     *   permanent redirection
+     * - `302` - the request should be redirected to the normalized URL using
+     *   temporary redirection
+     * - `404` - [[NotFoundHttpException]] will be thrown
+     * - `callable` - custom user callback, for example:
+     *
+     *   ```php
+     *   function ($route, $normalizer) {
+     *       // use custom action for redirections
+     *       $route[1]['oldRoute'] = $route[0];
+     *       $route[0] = 'site/redirect';
+     *       return $route;
+     *   }
+     *   ```
+     */
+    public $action = self::ACTION_REDIRECT_PERMANENT;
+
+
+    /**
+     * Performs normalization action for the specified $route.
+     * @param array $route route for normalization
+     * @return array normalized route
+     * @throws InvalidConfigException if invalid normalization action is used.
+     * @throws UrlNormalizerRedirectException if normalization requires redirection.
+     * @throws NotFoundHttpException if normalization suggests action matching route does not exist.
+     */
+    public function normalizeRoute($route)
+    {
+        if ($this->action === null) {
+            return $route;
+        } elseif ($this->action === static::ACTION_REDIRECT_PERMANENT || $this->action === static::ACTION_REDIRECT_TEMPORARY) {
+            throw new UrlNormalizerRedirectException([$route[0]] + $route[1], $this->action);
+        } elseif ($this->action === static::ACTION_NOT_FOUND) {
+            throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
+        } elseif (is_callable($this->action)) {
+            return call_user_func($this->action, $route, $this);
+        }
+
+        throw new InvalidConfigException('Invalid normalizer action.');
+    }
+
+    /**
+     * Normalizes specified pathInfo.
+     * @param string $pathInfo pathInfo for normalization
+     * @param string $suffix current rule suffix
+     * @param bool $normalized if specified, this variable will be set to `true` if $pathInfo
+     * was changed during normalization
+     * @return string normalized pathInfo
+     */
+    public function normalizePathInfo($pathInfo, $suffix, &$normalized = false)
+    {
+        if (empty($pathInfo)) {
+            return $pathInfo;
+        }
+
+        $sourcePathInfo = $pathInfo;
+        if ($this->collapseSlashes) {
+            $pathInfo = $this->collapseSlashes($pathInfo);
+        }
+
+        if ($this->normalizeTrailingSlash === true) {
+            $pathInfo = $this->normalizeTrailingSlash($pathInfo, $suffix);
+        }
+
+        $normalized = $sourcePathInfo !== $pathInfo;
+
+        return $pathInfo;
+    }
+
+    /**
+     * Collapse consecutive slashes in $pathInfo, for example converts `site///index` into `site/index`.
+     * @param string $pathInfo raw path info.
+     * @return string normalized path info.
+     */
+    protected function collapseSlashes($pathInfo)
+    {
+        return ltrim(preg_replace('#/{2,}#', '/', $pathInfo), '/');
+    }
+
+    /**
+     * Adds or removes trailing slashes from $pathInfo depending on whether the $suffix has a
+     * trailing slash or not.
+     * @param string $pathInfo raw path info.
+     * @param string $suffix
+     * @return string normalized path info.
+     */
+    protected function normalizeTrailingSlash($pathInfo, $suffix)
+    {
+        if (substr($suffix, -1) === '/' && substr($pathInfo, -1) !== '/') {
+            $pathInfo .= '/';
+        } elseif (substr($suffix, -1) !== '/' && substr($pathInfo, -1) === '/') {
+            $pathInfo = rtrim($pathInfo, '/');
+        }
+
+        return $pathInfo;
+    }
+}

+ 52 - 0
vendor/yiisoft/yii2/web/UrlNormalizerRedirectException.php

@@ -0,0 +1,52 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * UrlNormalizerRedirectException represents an information for redirection which should be
+ * performed during the URL normalization.
+ *
+ * @author Robert Korulczyk <robert@korulczyk.pl>
+ * @since 2.0.10
+ */
+class UrlNormalizerRedirectException extends \yii\base\Exception
+{
+    /**
+     * @var array|string the parameter to be used to generate a valid URL for redirection
+     * @see [[\yii\helpers\Url::to()]]
+     */
+    public $url;
+    /**
+     * @var bool|string the URI scheme to use in the generated URL for redirection
+     * @see [[\yii\helpers\Url::to()]]
+     */
+    public $scheme;
+    /**
+     * @var int the HTTP status code
+     */
+    public $statusCode;
+
+
+    /**
+     * @param array|string $url the parameter to be used to generate a valid URL for redirection.
+     * This will be used as first parameter for [[\yii\helpers\Url::to()]]
+     * @param int $statusCode HTTP status code used for redirection
+     * @param bool|string $scheme the URI scheme to use in the generated URL for redirection.
+     * This will be used as second parameter for [[\yii\helpers\Url::to()]]
+     * @param string $message the error message
+     * @param int $code the error code
+     * @param \Exception $previous the previous exception used for the exception chaining
+     */
+    public function __construct($url, $statusCode = 302, $scheme = false, $message = null, $code = 0, \Exception $previous = null)
+    {
+        $this->url = $url;
+        $this->scheme = $scheme;
+        $this->statusCode = $statusCode;
+        parent::__construct($message, $code, $previous);
+    }
+}

+ 602 - 0
vendor/yiisoft/yii2/web/UrlRule.php

@@ -0,0 +1,602 @@
+<?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-read 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, '/');
+    }
+}

+ 35 - 0
vendor/yiisoft/yii2/web/UrlRuleInterface.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * UrlRuleInterface is the interface that should be implemented by URL rule classes.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+interface UrlRuleInterface
+{
+    /**
+     * 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);
+
+    /**
+     * 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);
+}

+ 813 - 0
vendor/yiisoft/yii2/web/User.php

@@ -0,0 +1,813 @@
+<?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\Component;
+use yii\base\InvalidConfigException;
+use yii\base\InvalidValueException;
+use yii\di\Instance;
+use yii\rbac\CheckAccessInterface;
+
+/**
+ * User is the class for the `user` application component that manages the user authentication status.
+ *
+ * You may use [[isGuest]] to determine whether the current user is a guest or not.
+ * If the user is a guest, the [[identity]] property would return `null`. Otherwise, it would
+ * be an instance of [[IdentityInterface]].
+ *
+ * You may call various methods to change the user authentication status:
+ *
+ * - [[login()]]: sets the specified identity and remembers the authentication status in session and cookie;
+ * - [[logout()]]: marks the user as a guest and clears the relevant information from session and cookie;
+ * - [[setIdentity()]]: changes the user identity without touching session or cookie
+ *   (this is best used in stateless RESTful API implementation).
+ *
+ * Note that User only maintains the user authentication status. It does NOT handle how to authenticate
+ * a user. The logic of how to authenticate a user should be done in the class implementing [[IdentityInterface]].
+ * You are also required to set [[identityClass]] with the name of this class.
+ *
+ * User is configured as an application component in [[\yii\web\Application]] by default.
+ * You can access that instance via `Yii::$app->user`.
+ *
+ * You can modify its configuration by adding an array to your application config under `components`
+ * as it is shown in the following example:
+ *
+ * ```php
+ * 'user' => [
+ *     'identityClass' => 'app\models\User', // User must implement the IdentityInterface
+ *     'enableAutoLogin' => true,
+ *     // 'loginUrl' => ['user/login'],
+ *     // ...
+ * ]
+ * ```
+ *
+ * @property-read string|int $id The unique identifier for the user. If `null`, it means the user is a guest.
+ * This property is read-only.
+ * @property IdentityInterface|null $identity The identity object associated with the currently logged-in
+ * user. `null` is returned if the user is not logged in (not authenticated).
+ * @property-read bool $isGuest Whether the current user is a guest. This property is read-only.
+ * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type
+ * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class User extends Component
+{
+    const EVENT_BEFORE_LOGIN = 'beforeLogin';
+    const EVENT_AFTER_LOGIN = 'afterLogin';
+    const EVENT_BEFORE_LOGOUT = 'beforeLogout';
+    const EVENT_AFTER_LOGOUT = 'afterLogout';
+
+    /**
+     * @var string the class name of the [[identity]] object.
+     */
+    public $identityClass;
+    /**
+     * @var bool whether to enable cookie-based login. Defaults to `false`.
+     * Note that this property will be ignored if [[enableSession]] is `false`.
+     */
+    public $enableAutoLogin = false;
+    /**
+     * @var bool whether to use session to persist authentication status across multiple requests.
+     * You set this property to be `false` if your application is stateless, which is often the case
+     * for RESTful APIs.
+     */
+    public $enableSession = true;
+    /**
+     * @var string|array the URL for login when [[loginRequired()]] is called.
+     * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
+     * The first element of the array should be the route to the login action, and the rest of
+     * the name-value pairs are GET parameters used to construct the login URL. For example,
+     *
+     * ```php
+     * ['site/login', 'ref' => 1]
+     * ```
+     *
+     * If this property is `null`, a 403 HTTP exception will be raised when [[loginRequired()]] is called.
+     */
+    public $loginUrl = ['site/login'];
+    /**
+     * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is `true`.
+     * @see Cookie
+     */
+    public $identityCookie = ['name' => '_identity', 'httpOnly' => true];
+    /**
+     * @var int the number of seconds in which the user will be logged out automatically if the user
+     * remains inactive. If this property is not set, the user will be logged out after
+     * the current session expires (c.f. [[Session::timeout]]).
+     * Note that this will not work if [[enableAutoLogin]] is `true`.
+     */
+    public $authTimeout;
+    /**
+     * @var CheckAccessInterface|string|array The access checker object to use for checking access or the application
+     * component ID of the access checker.
+     * If not set the application auth manager will be used.
+     * @since 2.0.9
+     */
+    public $accessChecker;
+    /**
+     * @var int the number of seconds in which the user will be logged out automatically
+     * regardless of activity.
+     * Note that this will not work if [[enableAutoLogin]] is `true`.
+     */
+    public $absoluteAuthTimeout;
+    /**
+     * @var bool whether to automatically renew the identity cookie each time a page is requested.
+     * This property is effective only when [[enableAutoLogin]] is `true`.
+     * When this is `false`, the identity cookie will expire after the specified duration since the user
+     * is initially logged in. When this is `true`, the identity cookie will expire after the specified duration
+     * since the user visits the site the last time.
+     * @see enableAutoLogin
+     */
+    public $autoRenewCookie = true;
+    /**
+     * @var string the session variable name used to store the value of [[id]].
+     */
+    public $idParam = '__id';
+    /**
+     * @var string the session variable name used to store authentication key.
+     * @since 2.0.41
+     */
+    public $authKeyParam = '__authKey';
+    /**
+     * @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
+     * This is used when [[authTimeout]] is set.
+     */
+    public $authTimeoutParam = '__expire';
+    /**
+     * @var string the session variable name used to store the value of absolute expiration timestamp of the authenticated state.
+     * This is used when [[absoluteAuthTimeout]] is set.
+     */
+    public $absoluteAuthTimeoutParam = '__absoluteExpire';
+    /**
+     * @var string the session variable name used to store the value of [[returnUrl]].
+     */
+    public $returnUrlParam = '__returnUrl';
+    /**
+     * @var array MIME types for which this component should redirect to the [[loginUrl]].
+     * @since 2.0.8
+     */
+    public $acceptableRedirectTypes = ['text/html', 'application/xhtml+xml'];
+
+    private $_access = [];
+
+
+    /**
+     * Initializes the application component.
+     */
+    public function init()
+    {
+        parent::init();
+
+        if ($this->identityClass === null) {
+            throw new InvalidConfigException('User::identityClass must be set.');
+        }
+        if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) {
+            throw new InvalidConfigException('User::identityCookie must contain the "name" element.');
+        }
+        if ($this->accessChecker !== null) {
+            $this->accessChecker = Instance::ensure($this->accessChecker, '\yii\rbac\CheckAccessInterface');
+        }
+    }
+
+    private $_identity = false;
+
+    /**
+     * Returns the identity object associated with the currently logged-in user.
+     * When [[enableSession]] is true, this method may attempt to read the user's authentication data
+     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
+     * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before.
+     * This is only useful when [[enableSession]] is true.
+     * @return IdentityInterface|null the identity object associated with the currently logged-in user.
+     * `null` is returned if the user is not logged in (not authenticated).
+     * @see login()
+     * @see logout()
+     */
+    public function getIdentity($autoRenew = true)
+    {
+        if ($this->_identity === false) {
+            if ($this->enableSession && $autoRenew) {
+                try {
+                    $this->_identity = null;
+                    $this->renewAuthStatus();
+                } catch (\Exception $e) {
+                    $this->_identity = false;
+                    throw $e;
+                } catch (\Throwable $e) {
+                    $this->_identity = false;
+                    throw $e;
+                }
+            } else {
+                return null;
+            }
+        }
+
+        return $this->_identity;
+    }
+
+    /**
+     * Sets the user identity object.
+     *
+     * Note that this method does not deal with session or cookie. You should usually use [[switchIdentity()]]
+     * to change the identity of the current user.
+     *
+     * @param IdentityInterface|null $identity the identity object associated with the currently logged user.
+     * If null, it means the current user will be a guest without any associated identity.
+     * @throws InvalidValueException if `$identity` object does not implement [[IdentityInterface]].
+     */
+    public function setIdentity($identity)
+    {
+        if ($identity instanceof IdentityInterface) {
+            $this->_identity = $identity;
+        } elseif ($identity === null) {
+            $this->_identity = null;
+        } else {
+            throw new InvalidValueException('The identity object must implement IdentityInterface.');
+        }
+        $this->_access = [];
+    }
+
+    /**
+     * Logs in a user.
+     *
+     * After logging in a user:
+     * - the user's identity information is obtainable from the [[identity]] property
+     *
+     * If [[enableSession]] is `true`:
+     * - the identity information will be stored in session and be available in the next requests
+     * - in case of `$duration == 0`: as long as the session remains active or till the user closes the browser
+     * - in case of `$duration > 0`: as long as the session remains active or as long as the cookie
+     *   remains valid by it's `$duration` in seconds when [[enableAutoLogin]] is set `true`.
+     *
+     * If [[enableSession]] is `false`:
+     * - the `$duration` parameter will be ignored
+     *
+     * @param IdentityInterface $identity the user identity (which should already be authenticated)
+     * @param int $duration number of seconds that the user can remain in logged-in status, defaults to `0`
+     * @return bool whether the user is logged in
+     */
+    public function login(IdentityInterface $identity, $duration = 0)
+    {
+        if ($this->beforeLogin($identity, false, $duration)) {
+            $this->switchIdentity($identity, $duration);
+            $id = $identity->getId();
+            $ip = Yii::$app->getRequest()->getUserIP();
+            if ($this->enableSession) {
+                $log = "User '$id' logged in from $ip with duration $duration.";
+            } else {
+                $log = "User '$id' logged in from $ip. Session not enabled.";
+            }
+
+            $this->regenerateCsrfToken();
+
+            Yii::info($log, __METHOD__);
+            $this->afterLogin($identity, false, $duration);
+        }
+
+        return !$this->getIsGuest();
+    }
+
+    /**
+     * Regenerates CSRF token
+     *
+     * @since 2.0.14.2
+     */
+    protected function regenerateCsrfToken()
+    {
+        $request = Yii::$app->getRequest();
+        if ($request->enableCsrfCookie || $this->enableSession) {
+            $request->getCsrfToken(true);
+        }
+    }
+
+    /**
+     * Logs in a user by the given access token.
+     * This method will first authenticate the user by calling [[IdentityInterface::findIdentityByAccessToken()]]
+     * with the provided access token. If successful, it will call [[login()]] to log in the authenticated user.
+     * If authentication fails or [[login()]] is unsuccessful, it will return null.
+     * @param string $token the access token
+     * @param mixed $type the type of the token. The value of this parameter depends on the implementation.
+     * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`.
+     * @return IdentityInterface|null the identity associated with the given access token. Null is returned if
+     * the access token is invalid or [[login()]] is unsuccessful.
+     */
+    public function loginByAccessToken($token, $type = null)
+    {
+        /* @var $class IdentityInterface */
+        $class = $this->identityClass;
+        $identity = $class::findIdentityByAccessToken($token, $type);
+        if ($identity && $this->login($identity)) {
+            return $identity;
+        }
+
+        return null;
+    }
+
+    /**
+     * Logs in a user by cookie.
+     *
+     * This method attempts to log in a user using the ID and authKey information
+     * provided by the [[identityCookie|identity cookie]].
+     */
+    protected function loginByCookie()
+    {
+        $data = $this->getIdentityAndDurationFromCookie();
+        if (isset($data['identity'], $data['duration'])) {
+            $identity = $data['identity'];
+            $duration = $data['duration'];
+            if ($this->beforeLogin($identity, true, $duration)) {
+                $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
+                $id = $identity->getId();
+                $ip = Yii::$app->getRequest()->getUserIP();
+                Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
+                $this->afterLogin($identity, true, $duration);
+            }
+        }
+    }
+
+    /**
+     * Logs out the current user.
+     * This will remove authentication-related session data.
+     * If `$destroySession` is true, all session data will be removed.
+     * @param bool $destroySession whether to destroy the whole session. Defaults to true.
+     * This parameter is ignored if [[enableSession]] is false.
+     * @return bool whether the user is logged out
+     */
+    public function logout($destroySession = true)
+    {
+        $identity = $this->getIdentity();
+        if ($identity !== null && $this->beforeLogout($identity)) {
+            $this->switchIdentity(null);
+            $id = $identity->getId();
+            $ip = Yii::$app->getRequest()->getUserIP();
+            Yii::info("User '$id' logged out from $ip.", __METHOD__);
+            if ($destroySession && $this->enableSession) {
+                Yii::$app->getSession()->destroy();
+            }
+            $this->afterLogout($identity);
+        }
+
+        return $this->getIsGuest();
+    }
+
+    /**
+     * Returns a value indicating whether the user is a guest (not authenticated).
+     * @return bool whether the current user is a guest.
+     * @see getIdentity()
+     */
+    public function getIsGuest()
+    {
+        return $this->getIdentity() === null;
+    }
+
+    /**
+     * Returns a value that uniquely represents the user.
+     * @return string|int the unique identifier for the user. If `null`, it means the user is a guest.
+     * @see getIdentity()
+     */
+    public function getId()
+    {
+        $identity = $this->getIdentity();
+
+        return $identity !== null ? $identity->getId() : null;
+    }
+
+    /**
+     * Returns the URL that the browser should be redirected to after successful login.
+     *
+     * This method reads the return URL from the session. It is usually used by the login action which
+     * may call this method to redirect the browser to where it goes after successful authentication.
+     *
+     * @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 [[setReturnUrl()]] on accepted format of the URL.
+     * @return string the URL that the user should be redirected to after login.
+     * @see loginRequired()
+     */
+    public function getReturnUrl($defaultUrl = null)
+    {
+        $url = Yii::$app->getSession()->get($this->returnUrlParam, $defaultUrl);
+        if (is_array($url)) {
+            if (isset($url[0])) {
+                return Yii::$app->getUrlManager()->createUrl($url);
+            }
+
+            $url = null;
+        }
+
+        return $url === null ? Yii::$app->getHomeUrl() : $url;
+    }
+
+    /**
+     * Remembers the URL in the session so that it can be retrieved back later by [[getReturnUrl()]].
+     * @param string|array $url the URL that the user should be redirected to after login.
+     * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
+     * The first element of the array should be the route, and the rest of
+     * the name-value pairs are GET parameters used to construct the URL. For example,
+     *
+     * ```php
+     * ['admin/index', 'ref' => 1]
+     * ```
+     */
+    public function setReturnUrl($url)
+    {
+        Yii::$app->getSession()->set($this->returnUrlParam, $url);
+    }
+
+    /**
+     * Redirects the user browser to the login page.
+     *
+     * Before the redirection, the current URL (if it's not an AJAX url) will be kept as [[returnUrl]] so that
+     * the user browser may be redirected back to the current page after successful login.
+     *
+     * Make sure you set [[loginUrl]] so that the user browser can be redirected to the specified login URL after
+     * calling this method.
+     *
+     * Note that when [[loginUrl]] is set, calling this method will NOT terminate the application execution.
+     *
+     * @param bool $checkAjax whether to check if the request is an AJAX request. When this is true and the request
+     * is an AJAX request, the current URL (for AJAX request) will NOT be set as the return URL.
+     * @param bool $checkAcceptHeader whether to check if the request accepts HTML responses. Defaults to `true`. When this is true and
+     * the request does not accept HTML responses the current URL will not be SET as the return URL. Also instead of
+     * redirecting the user an ForbiddenHttpException is thrown. This parameter is available since version 2.0.8.
+     * @return Response the redirection response if [[loginUrl]] is set
+     * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set or a redirect is
+     * not applicable.
+     */
+    public function loginRequired($checkAjax = true, $checkAcceptHeader = true)
+    {
+        $request = Yii::$app->getRequest();
+        $canRedirect = !$checkAcceptHeader || $this->checkRedirectAcceptable();
+        if ($this->enableSession
+            && $request->getIsGet()
+            && (!$checkAjax || !$request->getIsAjax())
+            && $canRedirect
+        ) {
+            $this->setReturnUrl($request->getAbsoluteUrl());
+        }
+        if ($this->loginUrl !== null && $canRedirect) {
+            $loginUrl = (array) $this->loginUrl;
+            if ($loginUrl[0] !== Yii::$app->requestedRoute) {
+                return Yii::$app->getResponse()->redirect($this->loginUrl);
+            }
+        }
+        throw new ForbiddenHttpException(Yii::t('yii', 'Login Required'));
+    }
+
+    /**
+     * This method is called before logging in a user.
+     * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event.
+     * If you override this method, make sure you call the parent implementation
+     * so that the event is triggered.
+     * @param IdentityInterface $identity the user identity information
+     * @param bool $cookieBased whether the login is cookie-based
+     * @param int $duration number of seconds that the user can remain in logged-in status.
+     * If 0, it means login till the user closes the browser or the session is manually destroyed.
+     * @return bool whether the user should continue to be logged in
+     */
+    protected function beforeLogin($identity, $cookieBased, $duration)
+    {
+        $event = new UserEvent([
+            'identity' => $identity,
+            'cookieBased' => $cookieBased,
+            'duration' => $duration,
+        ]);
+        $this->trigger(self::EVENT_BEFORE_LOGIN, $event);
+
+        return $event->isValid;
+    }
+
+    /**
+     * This method is called after the user is successfully logged in.
+     * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event.
+     * If you override this method, make sure you call the parent implementation
+     * so that the event is triggered.
+     * @param IdentityInterface $identity the user identity information
+     * @param bool $cookieBased whether the login is cookie-based
+     * @param int $duration number of seconds that the user can remain in logged-in status.
+     * If 0, it means login till the user closes the browser or the session is manually destroyed.
+     */
+    protected function afterLogin($identity, $cookieBased, $duration)
+    {
+        $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([
+            'identity' => $identity,
+            'cookieBased' => $cookieBased,
+            'duration' => $duration,
+        ]));
+    }
+
+    /**
+     * This method is invoked when calling [[logout()]] to log out a user.
+     * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event.
+     * If you override this method, make sure you call the parent implementation
+     * so that the event is triggered.
+     * @param IdentityInterface $identity the user identity information
+     * @return bool whether the user should continue to be logged out
+     */
+    protected function beforeLogout($identity)
+    {
+        $event = new UserEvent([
+            'identity' => $identity,
+        ]);
+        $this->trigger(self::EVENT_BEFORE_LOGOUT, $event);
+
+        return $event->isValid;
+    }
+
+    /**
+     * This method is invoked right after a user is logged out via [[logout()]].
+     * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event.
+     * If you override this method, make sure you call the parent implementation
+     * so that the event is triggered.
+     * @param IdentityInterface $identity the user identity information
+     */
+    protected function afterLogout($identity)
+    {
+        $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([
+            'identity' => $identity,
+        ]));
+    }
+
+    /**
+     * Renews the identity cookie.
+     * This method will set the expiration time of the identity cookie to be the current time
+     * plus the originally specified cookie duration.
+     */
+    protected function renewIdentityCookie()
+    {
+        $name = $this->identityCookie['name'];
+        $value = Yii::$app->getRequest()->getCookies()->getValue($name);
+        if ($value !== null) {
+            $data = json_decode($value, true);
+            if (is_array($data) && isset($data[2])) {
+                $cookie = Yii::createObject(array_merge($this->identityCookie, [
+                    'class' => 'yii\web\Cookie',
+                    'value' => $value,
+                    'expire' => time() + (int) $data[2],
+                ]));
+                Yii::$app->getResponse()->getCookies()->add($cookie);
+            }
+        }
+    }
+
+    /**
+     * Sends an identity cookie.
+     * This method is used when [[enableAutoLogin]] is true.
+     * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login
+     * information in the cookie.
+     * @param IdentityInterface $identity
+     * @param int $duration number of seconds that the user can remain in logged-in status.
+     * @see loginByCookie()
+     */
+    protected function sendIdentityCookie($identity, $duration)
+    {
+        $cookie = Yii::createObject(array_merge($this->identityCookie, [
+            'class' => 'yii\web\Cookie',
+            'value' => json_encode([
+                $identity->getId(),
+                $identity->getAuthKey(),
+                $duration,
+            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
+            'expire' => time() + $duration,
+        ]));
+        Yii::$app->getResponse()->getCookies()->add($cookie);
+    }
+
+    /**
+     * Determines if an identity cookie has a valid format and contains a valid auth key.
+     * This method is used when [[enableAutoLogin]] is true.
+     * This method attempts to authenticate a user using the information in the identity cookie.
+     * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null.
+     * @see loginByCookie()
+     * @since 2.0.9
+     */
+    protected function getIdentityAndDurationFromCookie()
+    {
+        $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);
+        if ($value === null) {
+            return null;
+        }
+        $data = json_decode($value, true);
+        if (is_array($data) && count($data) == 3) {
+            list($id, $authKey, $duration) = $data;
+            /* @var $class IdentityInterface */
+            $class = $this->identityClass;
+            $identity = $class::findIdentity($id);
+            if ($identity !== null) {
+                if (!$identity instanceof IdentityInterface) {
+                    throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
+                } elseif (!$identity->validateAuthKey($authKey)) {
+                    $ip = Yii::$app->getRequest()->getUserIP();
+                    Yii::warning("Invalid cookie auth key attempted for user '$id' from $ip: $authKey", __METHOD__);
+                } else {
+                    return ['identity' => $identity, 'duration' => $duration];
+                }
+            }
+        }
+        $this->removeIdentityCookie();
+        return null;
+    }
+
+    /**
+     * Removes the identity cookie.
+     * This method is used when [[enableAutoLogin]] is true.
+     * @since 2.0.9
+     */
+    protected function removeIdentityCookie()
+    {
+        Yii::$app->getResponse()->getCookies()->remove(Yii::createObject(array_merge($this->identityCookie, [
+            'class' => 'yii\web\Cookie',
+        ])));
+    }
+
+    /**
+     * Switches to a new identity for the current user.
+     *
+     * When [[enableSession]] is true, this method may use session and/or cookie to store the user identity information,
+     * according to the value of `$duration`. Please refer to [[login()]] for more details.
+     *
+     * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]]
+     * when the current user needs to be associated with the corresponding identity information.
+     *
+     * @param IdentityInterface|null $identity the identity information to be associated with the current user.
+     * If null, it means switching the current user to be a guest.
+     * @param int $duration number of seconds that the user can remain in logged-in status.
+     * This parameter is used only when `$identity` is not null.
+     */
+    public function switchIdentity($identity, $duration = 0)
+    {
+        $this->setIdentity($identity);
+
+        if (!$this->enableSession) {
+            return;
+        }
+
+        /* Ensure any existing identity cookies are removed. */
+        if ($this->enableAutoLogin && ($this->autoRenewCookie || $identity === null)) {
+            $this->removeIdentityCookie();
+        }
+
+        $session = Yii::$app->getSession();
+        if (!YII_ENV_TEST) {
+            $session->regenerateID(true);
+        }
+        $session->remove($this->idParam);
+        $session->remove($this->authTimeoutParam);
+        $session->remove($this->authKeyParam);
+
+        if ($identity) {
+            $session->set($this->idParam, $identity->getId());
+            $session->set($this->authKeyParam, $identity->getAuthKey());
+            if ($this->authTimeout !== null) {
+                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
+            }
+            if ($this->absoluteAuthTimeout !== null) {
+                $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
+            }
+            if ($this->enableAutoLogin && $duration > 0) {
+                $this->sendIdentityCookie($identity, $duration);
+            }
+        }
+    }
+
+    /**
+     * Updates the authentication status using the information from session and cookie.
+     *
+     * This method will try to determine the user identity using the [[idParam]] session variable.
+     *
+     * If [[authTimeout]] is set, this method will refresh the timer.
+     *
+     * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]]
+     * if [[enableAutoLogin]] is true.
+     */
+    protected function renewAuthStatus()
+    {
+        $session = Yii::$app->getSession();
+        $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;
+
+        if ($id === null) {
+            $identity = null;
+        } else {
+            /* @var $class IdentityInterface */
+            $class = $this->identityClass;
+            $identity = $class::findIdentity($id);
+        }
+
+        if ($identity !== null) {
+            $authKey = $session->get($this->authKeyParam);
+            if ($authKey !== null && !$identity->validateAuthKey($authKey)) {
+                $identity = null;
+                $ip = Yii::$app->getRequest()->getUserIP();
+                Yii::warning("Invalid session auth key attempted for user '$id' from $ip: $authKey", __METHOD__);
+            }
+        }
+
+        $this->setIdentity($identity);
+
+        if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
+            $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null;
+            $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null;
+            if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
+                $this->logout(false);
+            } elseif ($this->authTimeout !== null) {
+                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
+            }
+        }
+
+        if ($this->enableAutoLogin) {
+            if ($this->getIsGuest()) {
+                $this->loginByCookie();
+            } elseif ($this->autoRenewCookie) {
+                $this->renewIdentityCookie();
+            }
+        }
+    }
+
+    /**
+     * Checks if the user can perform the operation as specified by the given permission.
+     *
+     * Note that you must configure "authManager" application component in order to use this method.
+     * Otherwise it will always return false.
+     *
+     * @param string $permissionName the name of the permission (e.g. "edit post") that needs access check.
+     * @param array $params name-value pairs that would be passed to the rules associated
+     * with the roles and permissions assigned to the user.
+     * @param bool $allowCaching whether to allow caching the result of access check.
+     * When this parameter is true (default), if the access check of an operation was performed
+     * before, its result will be directly returned when calling this method to check the same
+     * operation. If this parameter is false, this method will always call
+     * [[\yii\rbac\CheckAccessInterface::checkAccess()]] to obtain the up-to-date access result. Note that this
+     * caching is effective only within the same request and only works when `$params = []`.
+     * @return bool whether the user can perform the operation as specified by the given permission.
+     */
+    public function can($permissionName, $params = [], $allowCaching = true)
+    {
+        if ($allowCaching && empty($params) && isset($this->_access[$permissionName])) {
+            return $this->_access[$permissionName];
+        }
+        if (($accessChecker = $this->getAccessChecker()) === null) {
+            return false;
+        }
+        $access = $accessChecker->checkAccess($this->getId(), $permissionName, $params);
+        if ($allowCaching && empty($params)) {
+            $this->_access[$permissionName] = $access;
+        }
+
+        return $access;
+    }
+
+    /**
+     * Checks if the `Accept` header contains a content type that allows redirection to the login page.
+     * The login page is assumed to serve `text/html` or `application/xhtml+xml` by default. You can change acceptable
+     * content types by modifying [[acceptableRedirectTypes]] property.
+     * @return bool whether this request may be redirected to the login page.
+     * @see acceptableRedirectTypes
+     * @since 2.0.8
+     */
+    public function checkRedirectAcceptable()
+    {
+        $acceptableTypes = Yii::$app->getRequest()->getAcceptableContentTypes();
+        if (empty($acceptableTypes) || (count($acceptableTypes) === 1 && array_keys($acceptableTypes)[0] === '*/*')) {
+            return true;
+        }
+
+        foreach ($acceptableTypes as $type => $params) {
+            if (in_array($type, $this->acceptableRedirectTypes, true)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns auth manager associated with the user component.
+     *
+     * By default this is the `authManager` application component.
+     * You may override this method to return a different auth manager instance if needed.
+     * @return \yii\rbac\ManagerInterface
+     * @since 2.0.6
+     * @deprecated since version 2.0.9, to be removed in 2.1. Use [[getAccessChecker()]] instead.
+     */
+    protected function getAuthManager()
+    {
+        return Yii::$app->getAuthManager();
+    }
+
+    /**
+     * Returns the access checker used for checking access.
+     * @return CheckAccessInterface
+     * @since 2.0.9
+     */
+    protected function getAccessChecker()
+    {
+        return $this->accessChecker !== null ? $this->accessChecker : $this->getAuthManager();
+    }
+}

+ 40 - 0
vendor/yiisoft/yii2/web/UserEvent.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\Event;
+
+/**
+ * This event class is used for Events triggered by the [[User]] class.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class UserEvent extends Event
+{
+    /**
+     * @var IdentityInterface the identity object associated with this event
+     */
+    public $identity;
+    /**
+     * @var bool whether the login is cookie-based. This property is only meaningful
+     * for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_AFTER_LOGIN]] events.
+     */
+    public $cookieBased;
+    /**
+     * @var int number of seconds that the user can remain in logged-in status.
+     * If 0, it means login till the user closes the browser or the session is manually destroyed.
+     */
+    public $duration;
+    /**
+     * @var bool whether the login or logout should proceed.
+     * Event handlers may modify this property to determine whether the login or logout should proceed.
+     * This property is only meaningful for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_BEFORE_LOGOUT]] events.
+     */
+    public $isValid = true;
+}

+ 673 - 0
vendor/yiisoft/yii2/web/View.php

@@ -0,0 +1,673 @@
+<?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\InvalidConfigException;
+use yii\helpers\ArrayHelper;
+use yii\helpers\Html;
+use yii\helpers\Url;
+
+/**
+ * View represents a view object in the MVC pattern.
+ *
+ * View provides a set of methods (e.g. [[render()]]) for rendering purpose.
+ *
+ * View is configured as an application component in [[\yii\base\Application]] by default.
+ * You can access that instance via `Yii::$app->view`.
+ *
+ * You can modify its configuration by adding an array to your application config under `components`
+ * as it is shown in the following example:
+ *
+ * ```php
+ * 'view' => [
+ *     'theme' => 'app\themes\MyTheme',
+ *     'renderers' => [
+ *         // you may add Smarty or Twig renderer here
+ *     ]
+ *     // ...
+ * ]
+ * ```
+ *
+ * For more details and usage information on View, see the [guide article on views](guide:structure-views).
+ *
+ * @property \yii\web\AssetManager $assetManager The asset manager. Defaults to the "assetManager" application
+ * component.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class View extends \yii\base\View
+{
+    /**
+     * @event Event an event that is triggered by [[beginBody()]].
+     */
+    const EVENT_BEGIN_BODY = 'beginBody';
+    /**
+     * @event Event an event that is triggered by [[endBody()]].
+     */
+    const EVENT_END_BODY = 'endBody';
+    /**
+     * The location of registered JavaScript code block or files.
+     * This means the location is in the head section.
+     */
+    const POS_HEAD = 1;
+    /**
+     * The location of registered JavaScript code block or files.
+     * This means the location is at the beginning of the body section.
+     */
+    const POS_BEGIN = 2;
+    /**
+     * The location of registered JavaScript code block or files.
+     * This means the location is at the end of the body section.
+     */
+    const POS_END = 3;
+    /**
+     * The location of registered JavaScript code block.
+     * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`.
+     */
+    const POS_READY = 4;
+    /**
+     * The location of registered JavaScript code block.
+     * This means the JavaScript code block will be enclosed within `jQuery(window).load()`.
+     */
+    const POS_LOAD = 5;
+    /**
+     * This is internally used as the placeholder for receiving the content registered for the head section.
+     */
+    const PH_HEAD = '<![CDATA[YII-BLOCK-HEAD]]>';
+    /**
+     * This is internally used as the placeholder for receiving the content registered for the beginning of the body section.
+     */
+    const PH_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN]]>';
+    /**
+     * This is internally used as the placeholder for receiving the content registered for the end of the body section.
+     */
+    const PH_BODY_END = '<![CDATA[YII-BLOCK-BODY-END]]>';
+
+    /**
+     * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values
+     * are the registered [[AssetBundle]] objects.
+     * @see registerAssetBundle()
+     */
+    public $assetBundles = [];
+    /**
+     * @var string the page title
+     */
+    public $title;
+    /**
+     * @var array the registered meta tags.
+     * @see registerMetaTag()
+     */
+    public $metaTags = [];
+    /**
+     * @var array the registered link tags.
+     * @see registerLinkTag()
+     */
+    public $linkTags = [];
+    /**
+     * @var array the registered CSS code blocks.
+     * @see registerCss()
+     */
+    public $css = [];
+    /**
+     * @var array the registered CSS files.
+     * @see registerCssFile()
+     */
+    public $cssFiles = [];
+    /**
+     * @var array the registered JS code blocks
+     * @see registerJs()
+     */
+    public $js = [];
+    /**
+     * @var array the registered JS files.
+     * @see registerJsFile()
+     */
+    public $jsFiles = [];
+
+    private $_assetManager;
+
+
+    /**
+     * Marks the position of an HTML head section.
+     */
+    public function head()
+    {
+        echo self::PH_HEAD;
+    }
+
+    /**
+     * Marks the beginning of an HTML body section.
+     */
+    public function beginBody()
+    {
+        echo self::PH_BODY_BEGIN;
+        $this->trigger(self::EVENT_BEGIN_BODY);
+    }
+
+    /**
+     * Marks the ending of an HTML body section.
+     */
+    public function endBody()
+    {
+        $this->trigger(self::EVENT_END_BODY);
+        echo self::PH_BODY_END;
+
+        foreach (array_keys($this->assetBundles) as $bundle) {
+            $this->registerAssetFiles($bundle);
+        }
+    }
+
+    /**
+     * Marks the ending of an HTML page.
+     * @param bool $ajaxMode whether the view is rendering in AJAX mode.
+     * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions
+     * will be rendered at the end of the view like normal scripts.
+     */
+    public function endPage($ajaxMode = false)
+    {
+        $this->trigger(self::EVENT_END_PAGE);
+
+        $content = ob_get_clean();
+
+        echo strtr($content, [
+            self::PH_HEAD => $this->renderHeadHtml(),
+            self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(),
+            self::PH_BODY_END => $this->renderBodyEndHtml($ajaxMode),
+        ]);
+
+        $this->clear();
+    }
+
+    /**
+     * Renders a view in response to an AJAX request.
+     *
+     * This method is similar to [[render()]] except that it will surround the view being rendered
+     * with the calls of [[beginPage()]], [[head()]], [[beginBody()]], [[endBody()]] and [[endPage()]].
+     * By doing so, the method is able to inject into the rendering result with JS/CSS scripts and files
+     * that are registered with the view.
+     *
+     * @param string $view the view name. Please refer to [[render()]] on how to specify this parameter.
+     * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
+     * @param object $context the context that the view should use for rendering the view. If null,
+     * existing [[context]] will be used.
+     * @return string the rendering result
+     * @see render()
+     */
+    public function renderAjax($view, $params = [], $context = null)
+    {
+        $viewFile = $this->findViewFile($view, $context);
+
+        ob_start();
+        ob_implicit_flush(false);
+
+        $this->beginPage();
+        $this->head();
+        $this->beginBody();
+        echo $this->renderFile($viewFile, $params, $context);
+        $this->endBody();
+        $this->endPage(true);
+
+        return ob_get_clean();
+    }
+
+    /**
+     * Registers the asset manager being used by this view object.
+     * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component.
+     */
+    public function getAssetManager()
+    {
+        return $this->_assetManager ?: Yii::$app->getAssetManager();
+    }
+
+    /**
+     * Sets the asset manager.
+     * @param \yii\web\AssetManager $value the asset manager
+     */
+    public function setAssetManager($value)
+    {
+        $this->_assetManager = $value;
+    }
+
+    /**
+     * Clears up the registered meta tags, link tags, css/js scripts and files.
+     */
+    public function clear()
+    {
+        $this->metaTags = [];
+        $this->linkTags = [];
+        $this->css = [];
+        $this->cssFiles = [];
+        $this->js = [];
+        $this->jsFiles = [];
+        $this->assetBundles = [];
+    }
+
+    /**
+     * Registers all files provided by an asset bundle including depending bundles files.
+     * Removes a bundle from [[assetBundles]] once files are registered.
+     * @param string $name name of the bundle to register
+     */
+    protected function registerAssetFiles($name)
+    {
+        if (!isset($this->assetBundles[$name])) {
+            return;
+        }
+        $bundle = $this->assetBundles[$name];
+        if ($bundle) {
+            foreach ($bundle->depends as $dep) {
+                $this->registerAssetFiles($dep);
+            }
+            $bundle->registerAssetFiles($this);
+        }
+        unset($this->assetBundles[$name]);
+    }
+
+    /**
+     * Registers the named asset bundle.
+     * All dependent asset bundles will be registered.
+     * @param string $name the class name of the asset bundle (without the leading backslash)
+     * @param int|null $position if set, this forces a minimum position for javascript files.
+     * This will adjust depending assets javascript file position or fail if requirement can not be met.
+     * If this is null, asset bundles position settings will not be changed.
+     * See [[registerJsFile]] for more details on javascript position.
+     * @return AssetBundle the registered asset bundle instance
+     * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected
+     */
+    public function registerAssetBundle($name, $position = null)
+    {
+        if (!isset($this->assetBundles[$name])) {
+            $am = $this->getAssetManager();
+            $bundle = $am->getBundle($name);
+            $this->assetBundles[$name] = false;
+            // register dependencies
+            $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
+            foreach ($bundle->depends as $dep) {
+                $this->registerAssetBundle($dep, $pos);
+            }
+            $this->assetBundles[$name] = $bundle;
+        } elseif ($this->assetBundles[$name] === false) {
+            throw new InvalidConfigException("A circular dependency is detected for bundle '$name'.");
+        } else {
+            $bundle = $this->assetBundles[$name];
+        }
+
+        if ($position !== null) {
+            $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
+            if ($pos === null) {
+                $bundle->jsOptions['position'] = $pos = $position;
+            } elseif ($pos > $position) {
+                throw new InvalidConfigException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'.");
+            }
+            // update position for all dependencies
+            foreach ($bundle->depends as $dep) {
+                $this->registerAssetBundle($dep, $pos);
+            }
+        }
+
+        return $bundle;
+    }
+
+    /**
+     * Registers a meta tag.
+     *
+     * For example, a description meta tag can be added like the following:
+     *
+     * ```php
+     * $view->registerMetaTag([
+     *     'name' => 'description',
+     *     'content' => 'This website is about funny raccoons.'
+     * ]);
+     * ```
+     *
+     * will result in the meta tag `<meta name="description" content="This website is about funny raccoons.">`.
+     *
+     * @param array $options the HTML attributes for the meta tag.
+     * @param string $key the key that identifies the meta tag. If two meta tags are registered
+     * with the same key, the latter will overwrite the former. If this is null, the new meta tag
+     * will be appended to the existing ones.
+     */
+    public function registerMetaTag($options, $key = null)
+    {
+        if ($key === null) {
+            $this->metaTags[] = Html::tag('meta', '', $options);
+        } else {
+            $this->metaTags[$key] = Html::tag('meta', '', $options);
+        }
+    }
+
+    /**
+     * Registers CSRF meta tags.
+     * They are rendered dynamically to retrieve a new CSRF token for each request.
+     *
+     * ```php
+     * $view->registerCsrfMetaTags();
+     * ```
+     *
+     * The above code will result in `<meta name="csrf-param" content="[yii\web\Request::$csrfParam]">`
+     * and `<meta name="csrf-token" content="tTNpWKpdy-bx8ZmIq9R72...K1y8IP3XGkzZA==">` added to the page.
+     *
+     * Note: Hidden CSRF input of ActiveForm will be automatically refreshed by calling `window.yii.refreshCsrfToken()`
+     * from `yii.js`.
+     *
+     * @since 2.0.13
+     */
+    public function registerCsrfMetaTags()
+    {
+        $this->metaTags['csrf_meta_tags'] = $this->renderDynamic('return yii\helpers\Html::csrfMetaTags();');
+    }
+
+    /**
+     * Registers a link tag.
+     *
+     * For example, a link tag for a custom [favicon](http://www.w3.org/2005/10/howto-favicon)
+     * can be added like the following:
+     *
+     * ```php
+     * $view->registerLinkTag(['rel' => 'icon', 'type' => 'image/png', 'href' => '/myicon.png']);
+     * ```
+     *
+     * which will result in the following HTML: `<link rel="icon" type="image/png" href="/myicon.png">`.
+     *
+     * **Note:** To register link tags for CSS stylesheets, use [[registerCssFile()]] instead, which
+     * has more options for this kind of link tag.
+     *
+     * @param array $options the HTML attributes for the link tag.
+     * @param string $key the key that identifies the link tag. If two link tags are registered
+     * with the same key, the latter will overwrite the former. If this is null, the new link tag
+     * will be appended to the existing ones.
+     */
+    public function registerLinkTag($options, $key = null)
+    {
+        if ($key === null) {
+            $this->linkTags[] = Html::tag('link', '', $options);
+        } else {
+            $this->linkTags[$key] = Html::tag('link', '', $options);
+        }
+    }
+
+    /**
+     * Registers a CSS code block.
+     * @param string $css the content of the CSS code block to be registered
+     * @param array $options the HTML attributes for the `<style>`-tag.
+     * @param string $key the key that identifies the CSS code block. If null, it will use
+     * $css as the key. If two CSS code blocks are registered with the same key, the latter
+     * will overwrite the former.
+     */
+    public function registerCss($css, $options = [], $key = null)
+    {
+        $key = $key ?: md5($css);
+        $this->css[$key] = Html::style($css, $options);
+    }
+
+    /**
+     * Registers a CSS file.
+     *
+     * This method should be used for simple registration of CSS files. If you want to use features of
+     * [[AssetManager]] like appending timestamps to the URL and file publishing options, use [[AssetBundle]]
+     * and [[registerAssetBundle()]] instead.
+     *
+     * @param string $url the CSS file to be registered.
+     * @param array $options the HTML attributes for the link tag. Please refer to [[Html::cssFile()]] for
+     * the supported options. The following options are specially handled and are not treated as HTML attributes:
+     *
+     * - `depends`: array, specifies the names of the asset bundles that this CSS file depends on.
+     * - `appendTimestamp`: bool whether to append a timestamp to the URL.
+     *
+     * @param string $key the key that identifies the CSS script file. If null, it will use
+     * $url as the key. If two CSS files are registered with the same key, the latter
+     * will overwrite the former.
+     * @throws InvalidConfigException
+     */
+    public function registerCssFile($url, $options = [], $key = null)
+    {
+        $this->registerFile('css', $url, $options, $key);
+    }
+
+    /**
+     * Registers a JS code block.
+     * @param string $js the JS code block to be registered
+     * @param int $position the position at which the JS script tag should be inserted
+     * in a page. The possible values are:
+     *
+     * - [[POS_HEAD]]: in the head section
+     * - [[POS_BEGIN]]: at the beginning of the body section
+     * - [[POS_END]]: at the end of the body section
+     * - [[POS_LOAD]]: enclosed within jQuery(window).load().
+     *   Note that by using this position, the method will automatically register the jQuery js file.
+     * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value.
+     *   Note that by using this position, the method will automatically register the jQuery js file.
+     *
+     * @param string $key the key that identifies the JS code block. If null, it will use
+     * $js as the key. If two JS code blocks are registered with the same key, the latter
+     * will overwrite the former.
+     */
+    public function registerJs($js, $position = self::POS_READY, $key = null)
+    {
+        $key = $key ?: md5($js);
+        $this->js[$position][$key] = $js;
+        if ($position === self::POS_READY || $position === self::POS_LOAD) {
+            JqueryAsset::register($this);
+        }
+    }
+
+    /**
+     * Registers a JS or CSS file.
+     *
+     * @param string $url the JS file to be registered.
+     * @param string $type type (js or css) of the file.
+     * @param array $options the HTML attributes for the script tag. The following options are specially handled
+     * and are not treated as HTML attributes:
+     *
+     * - `depends`: array, specifies the names of the asset bundles that this CSS file depends on.
+     * - `appendTimestamp`: bool whether to append a timestamp to the URL.
+     *
+     * @param string $key the key that identifies the JS script file. If null, it will use
+     * $url as the key. If two JS files are registered with the same key at the same position, the latter
+     * will overwrite the former. Note that position option takes precedence, thus files registered with the same key,
+     * but different position option will not override each other.
+     * @throws InvalidConfigException
+     */
+    private function registerFile($type, $url, $options = [], $key = null)
+    {
+        $url = Yii::getAlias($url);
+        $key = $key ?: $url;
+        $depends = ArrayHelper::remove($options, 'depends', []);
+        $originalOptions = $options;
+        $position = ArrayHelper::remove($options, 'position', self::POS_END);
+
+        try {
+            $assetManagerAppendTimestamp = $this->getAssetManager()->appendTimestamp;
+        } catch (InvalidConfigException $e) {
+            $depends = null; // the AssetManager is not available
+            $assetManagerAppendTimestamp = false;
+        }
+        $appendTimestamp = ArrayHelper::remove($options, 'appendTimestamp', $assetManagerAppendTimestamp);
+
+        if (empty($depends)) {
+            // register directly without AssetManager
+            if ($appendTimestamp && Url::isRelative($url)) {
+                $prefix = Yii::getAlias('@web');
+                $prefixLength = strlen($prefix);
+                $trimmedUrl = ltrim((substr($url, 0, $prefixLength) === $prefix) ? substr($url, $prefixLength) : $url, '/');
+                $timestamp = @filemtime(Yii::getAlias('@webroot/' . $trimmedUrl, false));
+                if ($timestamp > 0) {
+                    $url = $timestamp ? "$url?v=$timestamp" : $url;
+                }
+            }
+            if ($type === 'js') {
+                $this->jsFiles[$position][$key] = Html::jsFile($url, $options);
+            } else {
+                $this->cssFiles[$key] = Html::cssFile($url, $options);
+            }
+        } else {
+            $this->getAssetManager()->bundles[$key] = Yii::createObject([
+                'class' => AssetBundle::className(),
+                'baseUrl' => '',
+                'basePath' => '@webroot',
+                (string)$type => [ArrayHelper::merge([!Url::isRelative($url) ? $url : ltrim($url, '/')], $originalOptions)],
+                "{$type}Options" => $options,
+                'depends' => (array)$depends,
+            ]);
+            $this->registerAssetBundle($key);
+        }
+    }
+
+    /**
+     * Registers a JS file.
+     *
+     * This method should be used for simple registration of JS files. If you want to use features of
+     * [[AssetManager]] like appending timestamps to the URL and file publishing options, use [[AssetBundle]]
+     * and [[registerAssetBundle()]] instead.
+     *
+     * @param string $url the JS file to be registered.
+     * @param array $options the HTML attributes for the script tag. The following options are specially handled
+     * and are not treated as HTML attributes:
+     *
+     * - `depends`: array, specifies the names of the asset bundles that this JS file depends on.
+     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
+     *     * [[POS_HEAD]]: in the head section
+     *     * [[POS_BEGIN]]: at the beginning of the body section
+     *     * [[POS_END]]: at the end of the body section. This is the default value.
+     * - `appendTimestamp`: bool whether to append a timestamp to the URL.
+     *
+     * Please refer to [[Html::jsFile()]] for other supported options.
+     *
+     * @param string $key the key that identifies the JS script file. If null, it will use
+     * $url as the key. If two JS files are registered with the same key at the same position, the latter
+     * will overwrite the former. Note that position option takes precedence, thus files registered with the same key,
+     * but different position option will not override each other.
+     * @throws InvalidConfigException
+     */
+    public function registerJsFile($url, $options = [], $key = null)
+    {
+        $this->registerFile('js', $url, $options, $key);
+    }
+
+    /**
+     * Registers a JS code block defining a variable. The name of variable will be
+     * used as key, preventing duplicated variable names.
+     *
+     * @param string $name Name of the variable
+     * @param array|string $value Value of the variable
+     * @param int $position the position in a page at which the JavaScript variable should be inserted.
+     * The possible values are:
+     *
+     * - [[POS_HEAD]]: in the head section. This is the default value.
+     * - [[POS_BEGIN]]: at the beginning of the body section.
+     * - [[POS_END]]: at the end of the body section.
+     * - [[POS_LOAD]]: enclosed within jQuery(window).load().
+     *   Note that by using this position, the method will automatically register the jQuery js file.
+     * - [[POS_READY]]: enclosed within jQuery(document).ready().
+     *   Note that by using this position, the method will automatically register the jQuery js file.
+     *
+     * @since 2.0.14
+     */
+    public function registerJsVar($name, $value, $position = self::POS_HEAD)
+    {
+        $js = sprintf('var %s = %s;', $name, \yii\helpers\Json::htmlEncode($value));
+        $this->registerJs($js, $position, $name);
+    }
+
+    /**
+     * Renders the content to be inserted in the head section.
+     * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
+     * @return string the rendered content
+     */
+    protected function renderHeadHtml()
+    {
+        $lines = [];
+        if (!empty($this->metaTags)) {
+            $lines[] = implode("\n", $this->metaTags);
+        }
+
+        if (!empty($this->linkTags)) {
+            $lines[] = implode("\n", $this->linkTags);
+        }
+        if (!empty($this->cssFiles)) {
+            $lines[] = implode("\n", $this->cssFiles);
+        }
+        if (!empty($this->css)) {
+            $lines[] = implode("\n", $this->css);
+        }
+        if (!empty($this->jsFiles[self::POS_HEAD])) {
+            $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]);
+        }
+        if (!empty($this->js[self::POS_HEAD])) {
+            $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]));
+        }
+
+        return empty($lines) ? '' : implode("\n", $lines);
+    }
+
+    /**
+     * Renders the content to be inserted at the beginning of the body section.
+     * The content is rendered using the registered JS code blocks and files.
+     * @return string the rendered content
+     */
+    protected function renderBodyBeginHtml()
+    {
+        $lines = [];
+        if (!empty($this->jsFiles[self::POS_BEGIN])) {
+            $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]);
+        }
+        if (!empty($this->js[self::POS_BEGIN])) {
+            $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]));
+        }
+
+        return empty($lines) ? '' : implode("\n", $lines);
+    }
+
+    /**
+     * Renders the content to be inserted at the end of the body section.
+     * The content is rendered using the registered JS code blocks and files.
+     * @param bool $ajaxMode whether the view is rendering in AJAX mode.
+     * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions
+     * will be rendered at the end of the view like normal scripts.
+     * @return string the rendered content
+     */
+    protected function renderBodyEndHtml($ajaxMode)
+    {
+        $lines = [];
+
+        if (!empty($this->jsFiles[self::POS_END])) {
+            $lines[] = implode("\n", $this->jsFiles[self::POS_END]);
+        }
+
+        if ($ajaxMode) {
+            $scripts = [];
+            if (!empty($this->js[self::POS_END])) {
+                $scripts[] = implode("\n", $this->js[self::POS_END]);
+            }
+            if (!empty($this->js[self::POS_READY])) {
+                $scripts[] = implode("\n", $this->js[self::POS_READY]);
+            }
+            if (!empty($this->js[self::POS_LOAD])) {
+                $scripts[] = implode("\n", $this->js[self::POS_LOAD]);
+            }
+            if (!empty($scripts)) {
+                $lines[] = Html::script(implode("\n", $scripts));
+            }
+        } else {
+            if (!empty($this->js[self::POS_END])) {
+                $lines[] = Html::script(implode("\n", $this->js[self::POS_END]));
+            }
+            if (!empty($this->js[self::POS_READY])) {
+                $js = "jQuery(function ($) {\n" . implode("\n", $this->js[self::POS_READY]) . "\n});";
+                $lines[] = Html::script($js);
+            }
+            if (!empty($this->js[self::POS_LOAD])) {
+                $js = "jQuery(window).on('load', function () {\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});";
+                $lines[] = Html::script($js);
+            }
+        }
+
+        return empty($lines) ? '' : implode("\n", $lines);
+    }
+}

+ 131 - 0
vendor/yiisoft/yii2/web/ViewAction.php

@@ -0,0 +1,131 @@
+<?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\Action;
+use yii\base\ViewNotFoundException;
+
+/**
+ * ViewAction represents an action that displays a view according to a user-specified parameter.
+ *
+ * By default, the view being displayed is specified via the `view` GET parameter.
+ * The name of the GET parameter can be customized via [[viewParam]].
+ *
+ * Users specify a view in the format of `path/to/view`, which translates to the view name
+ * `ViewPrefix/path/to/view` where `ViewPrefix` is given by [[viewPrefix]]. The view will then
+ * be rendered by the [[\yii\base\Controller::render()|render()]] method of the currently active controller.
+ *
+ * Note that the user-specified view name must start with a word character and can only contain
+ * word characters, forward slashes, dots and dashes.
+ *
+ * @author Alexander Makarov <sam@rmcreative.ru>
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ViewAction extends Action
+{
+    /**
+     * @var string the name of the GET parameter that contains the requested view name.
+     */
+    public $viewParam = 'view';
+    /**
+     * @var string the name of the default view when [[\yii\web\ViewAction::$viewParam]] GET parameter is not provided
+     * by user. Defaults to 'index'. This should be in the format of 'path/to/view', similar to that given in the
+     * GET parameter.
+     * @see \yii\web\ViewAction::$viewPrefix
+     */
+    public $defaultView = 'index';
+    /**
+     * @var string a string to be prefixed to the user-specified view name to form a complete view name.
+     * For example, if a user requests for `tutorial/chap1`, the corresponding view name will
+     * be `pages/tutorial/chap1`, assuming the prefix is `pages`.
+     * The actual view file is determined by [[\yii\base\View::findViewFile()]].
+     * @see \yii\base\View::findViewFile()
+     */
+    public $viewPrefix = 'pages';
+    /**
+     * @var mixed the name of the layout to be applied to the requested view.
+     * This will be assigned to [[\yii\base\Controller::$layout]] before the view is rendered.
+     * Defaults to null, meaning the controller's layout will be used.
+     * If false, no layout will be applied.
+     */
+    public $layout;
+
+
+    /**
+     * Runs the action.
+     * This method displays the view requested by the user.
+     * @throws NotFoundHttpException if the view file cannot be found
+     */
+    public function run()
+    {
+        $viewName = $this->resolveViewName();
+        $this->controller->actionParams[$this->viewParam] = Yii::$app->request->get($this->viewParam);
+
+        $controllerLayout = null;
+        if ($this->layout !== null) {
+            $controllerLayout = $this->controller->layout;
+            $this->controller->layout = $this->layout;
+        }
+
+        try {
+            $output = $this->render($viewName);
+
+            if ($controllerLayout) {
+                $this->controller->layout = $controllerLayout;
+            }
+        } catch (ViewNotFoundException $e) {
+            if ($controllerLayout) {
+                $this->controller->layout = $controllerLayout;
+            }
+
+            if (YII_DEBUG) {
+                throw new NotFoundHttpException($e->getMessage());
+            }
+
+            throw new NotFoundHttpException(
+                Yii::t('yii', 'The requested view "{name}" was not found.', ['name' => $viewName])
+            );
+        }
+
+        return $output;
+    }
+
+    /**
+     * Renders a view.
+     *
+     * @param string $viewName view name
+     * @return string result of the rendering
+     */
+    protected function render($viewName)
+    {
+        return $this->controller->render($viewName);
+    }
+
+    /**
+     * Resolves the view name currently being requested.
+     *
+     * @return string the resolved view name
+     * @throws NotFoundHttpException if the specified view name is invalid
+     */
+    protected function resolveViewName()
+    {
+        $viewName = Yii::$app->request->get($this->viewParam, $this->defaultView);
+
+        if (!is_string($viewName) || !preg_match('~^\w(?:(?!\/\.{0,2}\/)[\w\/\-\.])*$~', $viewName)) {
+            if (YII_DEBUG) {
+                throw new NotFoundHttpException("The requested view \"$viewName\" must start with a word character, must not contain /../ or /./, can contain only word characters, forward slashes, dots and dashes.");
+            }
+
+            throw new NotFoundHttpException(Yii::t('yii', 'The requested view "{name}" was not found.', ['name' => $viewName]));
+        }
+
+        return empty($this->viewPrefix) ? $viewName : $this->viewPrefix . '/' . $viewName;
+    }
+}

+ 185 - 0
vendor/yiisoft/yii2/web/XmlResponseFormatter.php

@@ -0,0 +1,185 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use DOMDocument;
+use DOMElement;
+use DOMException;
+use DOMText;
+use yii\base\Arrayable;
+use yii\base\Component;
+use yii\helpers\StringHelper;
+
+/**
+ * XmlResponseFormatter formats the given data into an XML response content.
+ *
+ * It is used by [[Response]] to format response data.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class XmlResponseFormatter extends Component implements ResponseFormatterInterface
+{
+    /**
+     * @var string the Content-Type header for the response
+     */
+    public $contentType = 'application/xml';
+    /**
+     * @var string the XML version
+     */
+    public $version = '1.0';
+    /**
+     * @var string the XML encoding. If not set, it will use the value of [[Response::charset]].
+     */
+    public $encoding;
+    /**
+     * @var string the name of the root element. If set to false, null or is empty then no root tag should be added.
+     */
+    public $rootTag = 'response';
+    /**
+     * @var string the name of the elements that represent the array elements with numeric keys.
+     */
+    public $itemTag = 'item';
+    /**
+     * @var bool whether to interpret objects implementing the [[\Traversable]] interface as arrays.
+     * Defaults to `true`.
+     * @since 2.0.7
+     */
+    public $useTraversableAsArray = true;
+    /**
+     * @var bool if object tags should be added
+     * @since 2.0.11
+     */
+    public $useObjectTags = true;
+
+
+    /**
+     * Formats the specified response.
+     * @param Response $response the response to be formatted.
+     */
+    public function format($response)
+    {
+        $charset = $this->encoding === null ? $response->charset : $this->encoding;
+        if (stripos($this->contentType, 'charset') === false) {
+            $this->contentType .= '; charset=' . $charset;
+        }
+        $response->getHeaders()->set('Content-Type', $this->contentType);
+        if ($response->data !== null) {
+            $dom = new DOMDocument($this->version, $charset);
+            if (!empty($this->rootTag)) {
+                $root = new DOMElement($this->rootTag);
+                $dom->appendChild($root);
+                $this->buildXml($root, $response->data);
+            } else {
+                $this->buildXml($dom, $response->data);
+            }
+            $response->content = $dom->saveXML();
+        }
+    }
+
+    /**
+     * @param DOMElement $element
+     * @param mixed $data
+     */
+    protected function buildXml($element, $data)
+    {
+        if (is_array($data) ||
+            ($data instanceof \Traversable && $this->useTraversableAsArray && !$data instanceof Arrayable)
+        ) {
+            foreach ($data as $name => $value) {
+                if (is_int($name) && is_object($value)) {
+                    $this->buildXml($element, $value);
+                } elseif (is_array($value) || is_object($value)) {
+                    $child = new DOMElement($this->getValidXmlElementName($name));
+                    $element->appendChild($child);
+                    $this->buildXml($child, $value);
+                } else {
+                    $child = new DOMElement($this->getValidXmlElementName($name));
+                    $element->appendChild($child);
+                    $child->appendChild(new DOMText($this->formatScalarValue($value)));
+                }
+            }
+        } elseif (is_object($data)) {
+            if ($this->useObjectTags) {
+                $child = new DOMElement(StringHelper::basename(get_class($data)));
+                $element->appendChild($child);
+            } else {
+                $child = $element;
+            }
+            if ($data instanceof Arrayable) {
+                $this->buildXml($child, $data->toArray());
+            } else {
+                $array = [];
+                foreach ($data as $name => $value) {
+                    $array[$name] = $value;
+                }
+                $this->buildXml($child, $array);
+            }
+        } else {
+            $element->appendChild(new DOMText($this->formatScalarValue($data)));
+        }
+    }
+
+    /**
+     * Formats scalar value to use in XML text node.
+     *
+     * @param int|string|bool|float $value a scalar value.
+     * @return string string representation of the value.
+     * @since 2.0.11
+     */
+    protected function formatScalarValue($value)
+    {
+        if ($value === true) {
+            return 'true';
+        }
+        if ($value === false) {
+            return 'false';
+        }
+        if (is_float($value)) {
+            return StringHelper::floatToString($value);
+        }
+        return (string) $value;
+    }
+
+    /**
+     * Returns element name ready to be used in DOMElement if
+     * name is not empty, is not int and is valid.
+     *
+     * Falls back to [[itemTag]] otherwise.
+     *
+     * @param mixed $name
+     * @return string
+     * @since 2.0.12
+     */
+    protected function getValidXmlElementName($name)
+    {
+        if (empty($name) || is_int($name) || !$this->isValidXmlName($name)) {
+            return $this->itemTag;
+        }
+
+        return $name;
+    }
+
+    /**
+     * Checks if name is valid to be used in XML.
+     *
+     * @param mixed $name
+     * @return bool
+     * @see http://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943
+     * @since 2.0.12
+     */
+    protected function isValidXmlName($name)
+    {
+        try {
+            new DOMElement($name);
+            return true;
+        } catch (DOMException $e) {
+            return false;
+        }
+    }
+}

+ 25 - 0
vendor/yiisoft/yii2/web/YiiAsset.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * This asset bundle provides the base JavaScript files for the Yii Framework.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class YiiAsset extends AssetBundle
+{
+    public $sourcePath = '@yii/assets';
+    public $js = [
+        'yii.js',
+    ];
+    public $depends = [
+        'yii\web\JqueryAsset',
+    ];
+}

+ 53 - 0
vendor/yiisoft/yii2/web/migrations/m160313_153426_session_init.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+use yii\db\Migration;
+
+/**
+ * Initializes Session tables.
+ *
+ * @author Misbahul D Munir <misbahuldmunir@gmail.com>
+ * @since 2.0.8
+ */
+class m160313_153426_session_init extends Migration
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function up()
+    {
+        $dataType = $this->binary();
+        $tableOptions = null;
+
+        switch ($this->db->driverName) {
+            case 'mysql':
+                // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
+                $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+                break;
+            case 'sqlsrv':
+            case 'mssql':
+            case 'dblib':
+                $dataType = $this->text();
+                break;
+        }
+
+        $this->createTable('{{%session}}', [
+            'id' => $this->string()->notNull(),
+            'expire' => $this->integer(),
+            'data' => $dataType,
+            'PRIMARY KEY ([[id]])',
+        ], $tableOptions);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function down()
+    {
+        $this->dropTable('{{%session}}');
+    }
+}

+ 21 - 0
vendor/yiisoft/yii2/web/migrations/schema-mssql.sql

@@ -0,0 +1,21 @@
+/**
+ * Database schema required by \yii\web\DbSession.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Misbahul D Munir <misbahuldmunir@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @since 2.0.8
+ */
+
+if object_id('[session]', 'U') is not null
+    drop table [session];
+
+create table [session]
+(
+    [id]  varchar(256) not null,
+    [expire] integer,
+    [data]   nvarchar(max),
+    primary key ([id])
+);

+ 20 - 0
vendor/yiisoft/yii2/web/migrations/schema-mysql.sql

@@ -0,0 +1,20 @@
+/**
+ * Database schema required by \yii\web\DbSession.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Misbahul D Munir <misbahuldmunir@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @since 2.0.8
+ */
+
+drop table if exists `session`;
+
+create table `session`
+(
+    `id`  varchar(256) not null,
+    `expire` integer,
+    `data`   LONGBLOB,
+    primary key (`id`)
+) engine InnoDB;

+ 20 - 0
vendor/yiisoft/yii2/web/migrations/schema-oci.sql

@@ -0,0 +1,20 @@
+/**
+ * Database schema required by \yii\web\DbSession.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Misbahul D Munir <misbahuldmunir@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @since 2.0.8
+ */
+
+drop table if exists "session";
+
+create table "session"
+(
+    "id"  varchar(256) not null,
+    "expire" integer,
+    "data"   BYTEA,
+    primary key ("id")
+);

+ 20 - 0
vendor/yiisoft/yii2/web/migrations/schema-pgsql.sql

@@ -0,0 +1,20 @@
+/**
+ * Database schema required by \yii\web\DbSession.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Misbahul D Munir <misbahuldmunir@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @since 2.0.8
+ */
+
+drop table if exists "session";
+
+create table "session"
+(
+    "id"  varchar(256) not null,
+    "expire" integer,
+    "data"   bytea,
+    primary key ("id")
+);

+ 20 - 0
vendor/yiisoft/yii2/web/migrations/schema-sqlite.sql

@@ -0,0 +1,20 @@
+/**
+ * Database schema required by \yii\web\DbSession.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Misbahul D Munir <misbahuldmunir@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @since 2.0.8
+ */
+
+drop table if exists "session";
+
+create table "session"
+(
+    "id"  varchar(256) not null,
+    "expire" integer,
+    "data"   BLOB,
+    primary key ("id")
+);