diff --git a/apps/advanced/backend/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php index 84ad1ad..9f66a11 100644 --- a/apps/advanced/backend/views/layouts/main.php +++ b/apps/advanced/backend/views/layouts/main.php @@ -24,7 +24,7 @@ AppAsset::register($this); <div class="masthead"> <h3 class="muted">My Company</h3> - <div class="navbar"> + <div class="navbar fullwidth"> <div class="navbar-inner"> <div class="container"> <?php echo Menu::widget(array( diff --git a/apps/advanced/backend/web/css/site.css b/apps/advanced/backend/web/css/site.css index 890a953..9604477 100644 --- a/apps/advanced/backend/web/css/site.css +++ b/apps/advanced/backend/web/css/site.css @@ -44,35 +44,35 @@ body { } /* Customize the navbar links to be fill the entire space of the .navbar */ -.navbar .navbar-inner { +.navbar.fullwidth .navbar-inner { padding: 0; } -.navbar .nav { +.navbar.fullwidth .nav { margin: 0; display: table; width: 100%; } -.navbar .nav li { +.navbar.fullwidth .nav li { display: table-cell; width: 1%; float: none; } -.navbar .nav li a { +.navbar.fullwidth .nav li a { font-weight: bold; text-align: center; border-left: 1px solid rgba(255, 255, 255, .75); border-right: 1px solid rgba(0, 0, 0, .1); } -.navbar .nav li:first-child a { +.navbar.fullwidth .nav li:first-child a { border-left: 0; border-radius: 3px 0 0 3px; } -.navbar .nav li:last-child a { +.navbar.fullwidth .nav li:last-child a { border-right: 0; border-radius: 0 3px 3px 0; } diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index 285b553..c4f7f53 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -16,7 +16,7 @@ class SiteController extends Controller { return array( 'captcha' => array( - 'class' => 'yii\web\CaptchaAction', + 'class' => 'yii\captcha\CaptchaAction', ), ); } diff --git a/apps/advanced/frontend/views/emails/passwordResetToken.php b/apps/advanced/frontend/views/emails/passwordResetToken.php index 5ab5df1..11aa8e9 100644 --- a/apps/advanced/frontend/views/emails/passwordResetToken.php +++ b/apps/advanced/frontend/views/emails/passwordResetToken.php @@ -6,7 +6,7 @@ use yii\helpers\Html; * @var common\models\User $user; */ -$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/resetPassword', array('token' => $user->password_reset_token)); +$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', array('token' => $user->password_reset_token)); ?> Hello <?php echo Html::encode($user->username)?>, diff --git a/apps/advanced/frontend/views/layouts/main.php b/apps/advanced/frontend/views/layouts/main.php index f123892..864992b 100644 --- a/apps/advanced/frontend/views/layouts/main.php +++ b/apps/advanced/frontend/views/layouts/main.php @@ -25,7 +25,7 @@ AppAsset::register($this); <div class="masthead"> <h3 class="muted">My Company</h3> - <div class="navbar"> + <div class="navbar fullwidth"> <div class="navbar-inner"> <div class="container"> <?php diff --git a/apps/advanced/frontend/views/site/contact.php b/apps/advanced/frontend/views/site/contact.php index 62bb9ef..3a3fb0c 100644 --- a/apps/advanced/frontend/views/site/contact.php +++ b/apps/advanced/frontend/views/site/contact.php @@ -1,7 +1,7 @@ <?php use yii\helpers\Html; use yii\widgets\ActiveForm; -use yii\widgets\Captcha; +use yii\captcha\Captcha; /** * @var yii\base\View $this diff --git a/apps/advanced/frontend/web/css/site.css b/apps/advanced/frontend/web/css/site.css index 890a953..9604477 100644 --- a/apps/advanced/frontend/web/css/site.css +++ b/apps/advanced/frontend/web/css/site.css @@ -44,35 +44,35 @@ body { } /* Customize the navbar links to be fill the entire space of the .navbar */ -.navbar .navbar-inner { +.navbar.fullwidth .navbar-inner { padding: 0; } -.navbar .nav { +.navbar.fullwidth .nav { margin: 0; display: table; width: 100%; } -.navbar .nav li { +.navbar.fullwidth .nav li { display: table-cell; width: 1%; float: none; } -.navbar .nav li a { +.navbar.fullwidth .nav li a { font-weight: bold; text-align: center; border-left: 1px solid rgba(255, 255, 255, .75); border-right: 1px solid rgba(0, 0, 0, .1); } -.navbar .nav li:first-child a { +.navbar.fullwidth .nav li:first-child a { border-left: 0; border-radius: 3px 0 0 3px; } -.navbar .nav li:last-child a { +.navbar.fullwidth .nav li:last-child a { border-right: 0; border-radius: 0 3px 3px 0; } diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index b2eec12..04da8b3 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -13,7 +13,7 @@ class SiteController extends Controller { return array( 'captcha' => array( - 'class' => 'yii\web\CaptchaAction', + 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_DEV ? 'testme' : null, ), ); diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 726ad41..5f8603f 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -23,7 +23,7 @@ app\config\AppAsset::register($this); <div class="masthead"> <h3 class="muted">My Company</h3> - <div class="navbar"> + <div class="navbar fullwidth"> <div class="navbar-inner"> <div class="container"> <?php echo Menu::widget(array( diff --git a/apps/basic/views/site/contact.php b/apps/basic/views/site/contact.php index c0e3bff..1997267 100644 --- a/apps/basic/views/site/contact.php +++ b/apps/basic/views/site/contact.php @@ -1,7 +1,7 @@ <?php use yii\helpers\Html; use yii\widgets\ActiveForm; -use yii\widgets\Captcha; +use yii\captcha\Captcha; /** * @var yii\base\View $this diff --git a/apps/basic/web/css/site.css b/apps/basic/web/css/site.css index 890a953..9604477 100644 --- a/apps/basic/web/css/site.css +++ b/apps/basic/web/css/site.css @@ -44,35 +44,35 @@ body { } /* Customize the navbar links to be fill the entire space of the .navbar */ -.navbar .navbar-inner { +.navbar.fullwidth .navbar-inner { padding: 0; } -.navbar .nav { +.navbar.fullwidth .nav { margin: 0; display: table; width: 100%; } -.navbar .nav li { +.navbar.fullwidth .nav li { display: table-cell; width: 1%; float: none; } -.navbar .nav li a { +.navbar.fullwidth .nav li a { font-weight: bold; text-align: center; border-left: 1px solid rgba(255, 255, 255, .75); border-right: 1px solid rgba(0, 0, 0, .1); } -.navbar .nav li:first-child a { +.navbar.fullwidth .nav li:first-child a { border-left: 0; border-radius: 3px 0 0 3px; } -.navbar .nav li:last-child a { +.navbar.fullwidth .nav li:last-child a { border-right: 0; border-radius: 0 3px 3px 0; } diff --git a/docs/guide/controller.md b/docs/guide/controller.md index c550b42..29918f0 100644 --- a/docs/guide/controller.md +++ b/docs/guide/controller.md @@ -50,6 +50,9 @@ If controller is located inside a module its action internal route will be `modu In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404. +> Note: If controller name or action name contains camelCased words, internal route will use dashes i.e. for +`DateTimeController::actionFastForward` route will be `date-time/fast-forward`. + ### Defaults If user isn't specifying any route i.e. using URL like `http://example.com/`, Yii assumes that default route should be diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 1873d47..5447635 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -1,7 +1,23 @@ Installation ============ -Installation of Yii mainly involves the following two steps: +Installing via Composer +----------------------- + +The recommended way of installing Yii is by using Composer package manager. + +There are two application templates available: + +- [basic](https://github.com/yiisoft/yii2-app-basic) that is just a basic frontend application template. +- [advanced](https://github.com/yiisoft/yii2-app-advanced) that is a set of frontend, backend, console, common + (shared code) and environments support. + +Please refer to installation instructions on these pages. + +Installing from zip +------------------- + +Installation from zip mainly involves the following two steps: 1. Download Yii Framework from [yiiframework.com](http://www.yiiframework.com/). 2. Unpack the Yii release file to a Web-accessible directory. @@ -12,7 +28,6 @@ needs to be exposed to Web users. Other PHP scripts, including those from Yii, should be protected from Web access; otherwise they might be exploited by hackers. - Requirements ------------ diff --git a/docs/guide/overview.md b/docs/guide/overview.md index 9e54fd4..c7eeb42 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -31,6 +31,10 @@ management systems (CMS), e-commerce systems, etc. How does Yii Compare with Other Frameworks? ------------------------------------------- -Like most PHP frameworks, Yii is an MVC (Model-View-Controller) framework. - -TBD \ No newline at end of file +- Like most PHP frameworks, Yii is an MVC (Model-View-Controller) framework. +- It is a fullstack framework providing many solutions and components such as logging, session management, caching etc. +- It has a good balance of simplicity and features. +- Syntax and overall development usability are taken seriously. +- Performance is one of the key goals. +- We are constantly watching other web frameworks out there and getting the best ideas in. Initial Yii release was heavily + influenced by Ruby on Rails. Still, we aren't blindly copying anyhting. \ No newline at end of file diff --git a/docs/guide/performance.md b/docs/guide/performance.md index 1fa3529..3f83dae 100644 --- a/docs/guide/performance.md +++ b/docs/guide/performance.md @@ -100,6 +100,8 @@ You can use `CacheSession` to store sessions using cache. Note that some cache storage such as memcached has no guarantee that session data will not be lost leading to unexpected logouts. +If you have [Redis](http://redis.io/) on your server, it's highly recommended as session storage. + Improving application --------------------- diff --git a/docs/guide/security.md b/docs/guide/security.md index e69de29..0a3a14e 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -0,0 +1,81 @@ +Security +======== + +Hashing and verifyig passwords +------------------------------ + +It is important not to store passwords in plain text but, contrary to popular belief, just using `md5` or `sha1` to +compute and verify hashes isn't a good way either. Modern hardware allows to brute force these very fast. + +In order to truly secure user passwords even in case your database is leaked you need to use a function that is resistant +to brute-force such as bcrypt. In PHP it can be achieved by using [crypt function](http://php.net/manual/en/function.crypt.php) +but since usage isn't trivial and one can easily misuse it, Yii provides two helper functions for generating hash from +password and verifying existing hash. + +When user sets his password we're taking password string from POST and then getting a hash: + +```php +$hash = \yii\helpers\Security::generatePasswordHash($password); +``` + +The hash we've got is persisted to database to be used later. + +Then when user is trying to log in we're verifying the password he entered against a hash that we've previously persisted: + +```php +if(Security::validatePassword($password, $hash)) { + // all good, logging user in +} +else { + // wrong password +} +``` + + +Random data +----------- + +Random data is useful in many cases. For example, when resetting a password via email you need to generate a token, +save it to database and send it via email to end user so he's able to prove that email belongs to him. It is very +important for this token to be truly unique else there will be a possibility to predict a value and reset another user's +password. + +Yii security helper makes it as simple as: + +```php +$key = \yii\helpers\Security::generateRandomKey(); +``` + +Encryption and decryption +------------------------- + +In order to encrypt data so only person knowing a secret passphrase or having a secret key will be able to decrypt it. +For example, we need to store some information in our database but we need to make sure only user knowing a secret code +can view it (even if database is leaked): + + +```php +// $data and $secretWord are from the form +$encryptedData = \yii\helpers\Security::encrypt($data, $secretWord); +// store $encryptedData to database +``` + +Then when user want to read it: + +```php +// $secretWord is from the form, $encryptedData is from database +$data = \yii\helpers\Security::decrypt($encryptedData, $secretWord); +``` + +Making sure data wasn't modified +-------------------------------- + +hashData() +validateData() + + +Securing Cookies +---------------- + +- validation +- httpOnly \ No newline at end of file diff --git a/extensions/twig/composer.json b/extensions/twig/composer.json index 0ff7437..e613807 100644 --- a/extensions/twig/composer.json +++ b/extensions/twig/composer.json @@ -20,7 +20,7 @@ "minimum-stability": "dev", "require": { "yiisoft/yii2": "*", - "twig/twig": "v1.13.0" + "twig/twig": "v1.13.2" }, "autoload": { "psr-0": { "yii\\twig": "" } diff --git a/framework/yii/assets.php b/framework/yii/assets.php index dd657f3..c52bca6 100644 --- a/framework/yii/assets.php +++ b/framework/yii/assets.php @@ -6,6 +6,6 @@ return array( yii\validators\PunycodeAsset::className(), yii\validators\ValidationAsset::className(), yii\widgets\ActiveFormAsset::className(), - yii\widgets\CaptchaAsset::className(), + yii\captcha\CaptchaAsset::className(), yii\widgets\MaskedInputAsset::className(), ); diff --git a/framework/yii/assets/yii.captcha.js b/framework/yii/assets/yii.captcha.js index 7dfe3cc..af14faa 100644 --- a/framework/yii/assets/yii.captcha.js +++ b/framework/yii/assets/yii.captcha.js @@ -1,7 +1,7 @@ /** * Yii Captcha widget. * - * This is the JavaScript widget used by the yii\widgets\Captcha widget. + * This is the JavaScript widget used by the yii\captcha\Captcha widget. * * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 0381e16..687f1a3 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -180,6 +180,16 @@ abstract class Application extends Module } /** + * Returns an ID that uniquely identifies this module among all modules within the current application. + * Since this is an application instance, it will always return an empty string. + * @return string the unique ID of the module. + */ + public function getUniqueId() + { + return ''; + } + + /** * Runs the application. * This is the main entrance of an application. * @return integer the exit status (0 means normal, non-zero values mean abnormal) @@ -475,6 +485,8 @@ abstract class Application extends Module */ public function handleFatalError() { + unset($this->_memoryReserve); + // load ErrorException manually here because autoloading them will not work // when error occurs while autoloading a class if (!class_exists('\\yii\\base\\Exception', false)) { @@ -487,7 +499,6 @@ abstract class Application extends Module $error = error_get_last(); if (ErrorException::isFatalError($error)) { - unset($this->_memoryReserve); $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); // use error_log because it's too late to use Yii log error_log($exception); diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index 8e75835..5ff8ae4 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -220,19 +220,19 @@ class Component extends Object * * - the class has a getter or setter method associated with the specified name * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVar` is true); - * - an attached behavior has a property of the given name (when `$checkBehavior` is true). + * - the class has a member variable with the specified name (when `$checkVars` is true); + * - an attached behavior has a property of the given name (when `$checkBehaviors` is true). * * @param string $name the property name - * @param boolean $checkVar whether to treat member variables as properties - * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component + * @param boolean $checkVars whether to treat member variables as properties + * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @return boolean whether the property is defined * @see canGetProperty * @see canSetProperty */ - public function hasProperty($name, $checkVar = true, $checkBehavior = true) + public function hasProperty($name, $checkVars = true, $checkBehaviors = true) { - return $this->canGetProperty($name, $checkVar, $checkBehavior) || $this->canSetProperty($name, $checkVar, $checkBehavior); + return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors); } /** @@ -241,23 +241,23 @@ class Component extends Object * * - the class has a getter method associated with the specified name * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVar` is true); - * - an attached behavior has a readable property of the given name (when `$checkBehavior` is true). + * - the class has a member variable with the specified name (when `$checkVars` is true); + * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true). * * @param string $name the property name - * @param boolean $checkVar whether to treat member variables as properties - * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component + * @param boolean $checkVars whether to treat member variables as properties + * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @return boolean whether the property can be read * @see canSetProperty */ - public function canGetProperty($name, $checkVar = true, $checkBehavior = true) + public function canGetProperty($name, $checkVars = true, $checkBehaviors = true) { - if (method_exists($this, 'get' . $name) || $checkVar && property_exists($this, $name)) { + if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) { return true; - } else { + } elseif ($checkBehaviors) { $this->ensureBehaviors(); foreach ($this->_behaviors as $behavior) { - if ($behavior->canGetProperty($name, $checkVar)) { + if ($behavior->canGetProperty($name, $checkVars)) { return true; } } @@ -271,23 +271,23 @@ class Component extends Object * * - the class has a setter method associated with the specified name * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVar` is true); - * - an attached behavior has a writable property of the given name (when `$checkBehavior` is true). + * - the class has a member variable with the specified name (when `$checkVars` is true); + * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true). * * @param string $name the property name - * @param boolean $checkVar whether to treat member variables as properties - * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component + * @param boolean $checkVars whether to treat member variables as properties + * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @return boolean whether the property can be written * @see canGetProperty */ - public function canSetProperty($name, $checkVar = true, $checkBehavior = true) + public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) { - if (method_exists($this, 'set' . $name) || $checkVar && property_exists($this, $name)) { + if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) { return true; - } else { + } elseif ($checkBehaviors) { $this->ensureBehaviors(); foreach ($this->_behaviors as $behavior) { - if ($behavior->canSetProperty($name, $checkVar)) { + if ($behavior->canSetProperty($name, $checkVars)) { return true; } } diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 39c87b0..99428fc 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -192,11 +192,16 @@ class ErrorHandler extends Component */ public function renderFile($_file_, $_params_) { - ob_start(); - ob_implicit_flush(false); - extract($_params_, EXTR_OVERWRITE); - require(Yii::getAlias($_file_)); - return ob_get_clean(); + $_params_['handler'] = $this; + if ($this->exception instanceof ErrorException) { + ob_start(); + ob_implicit_flush(false); + extract($_params_, EXTR_OVERWRITE); + require(Yii::getAlias($_file_)); + return ob_get_clean(); + } else { + return Yii::$app->getView()->renderFile($_file_, $_params_, $this); + } } /** diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 5326ed8..d9b5778 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -71,22 +71,36 @@ class Formatter extends Component } /** - * Formats the value based on the given type. + * Formats the value based on the given format type. * This method will call one of the "as" methods available in this class to do the formatting. - * For type "xyz", the method "asXyz" will be used. For example, if the type is "html", - * then [[asHtml()]] will be used. Type names are case insensitive. + * For type "xyz", the method "asXyz" will be used. For example, if the format is "html", + * then [[asHtml()]] will be used. Format names are case insensitive. * @param mixed $value the value to be formatted - * @param string $type the type of the value, e.g., "html", "text". + * @param string|array $format the format of the value, e.g., "html", "text". To specify additional + * parameters of the formatting method, you may use an array. The first element of the array + * specifies the format name, while the rest of the elements will be used as the parameters to the formatting + * method. For example, a format of `array('date', 'Y-m-d')` will cause the invocation of `asDate($value, 'Y-m-d')`. * @return string the formatting result * @throws InvalidParamException if the type is not supported by this class. */ - public function format($value, $type) + public function format($value, $format) { - $method = 'as' . $type; + if (is_array($format)) { + if (!isset($format[0])) { + throw new InvalidParamException('The $format array must contain at least one element.'); + } + $f = $format[0]; + $format[0] = $value; + $params = $format; + $format = $f; + } else { + $params = array($value); + } + $method = 'as' . $format; if (method_exists($this, $method)) { - return $this->$method($value); + return call_user_func_array(array($this, $method), $params); } else { - throw new InvalidParamException("Unknown type: $type"); + throw new InvalidParamException("Unknown type: $format"); } } diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index d2c8aa5..bb0f4b1 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -117,8 +117,8 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess * array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'), * // an inline validator defined via the "authenticate()" method in the model class * array('password', 'authenticate', 'on' => 'login'), - * // a validator of class "CaptchaValidator" - * array('captcha', 'CaptchaValidator'), + * // a validator of class "DateRangeValidator" + * array('dateRange', 'DateRangeValidator'), * ); * ~~~ * diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 2bc42e3..a85385b 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -192,13 +192,7 @@ abstract class Module extends Component */ public function getUniqueId() { - if ($this instanceof Application) { - return ''; - } elseif ($this->module) { - return ltrim($this->module->getUniqueId() . '/' . $this->id, '/'); - } else { - return $this->id; - } + return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id; } /** diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php index 7724008..50ad9e9 100644 --- a/framework/yii/base/Object.php +++ b/framework/yii/base/Object.php @@ -170,17 +170,17 @@ class Object implements Arrayable * * - the class has a getter or setter method associated with the specified name * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVar` is true); + * - the class has a member variable with the specified name (when `$checkVars` is true); * * @param string $name the property name - * @param boolean $checkVar whether to treat member variables as properties + * @param boolean $checkVars whether to treat member variables as properties * @return boolean whether the property is defined * @see canGetProperty * @see canSetProperty */ - public function hasProperty($name, $checkVar = true) + public function hasProperty($name, $checkVars = true) { - return $this->canGetProperty($name, $checkVar) || $this->canSetProperty($name, false); + return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false); } /** @@ -189,16 +189,16 @@ class Object implements Arrayable * * - the class has a getter method associated with the specified name * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVar` is true); + * - the class has a member variable with the specified name (when `$checkVars` is true); * * @param string $name the property name - * @param boolean $checkVar whether to treat member variables as properties + * @param boolean $checkVars whether to treat member variables as properties * @return boolean whether the property can be read * @see canSetProperty */ - public function canGetProperty($name, $checkVar = true) + public function canGetProperty($name, $checkVars = true) { - return method_exists($this, 'get' . $name) || $checkVar && property_exists($this, $name); + return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name); } /** @@ -207,16 +207,16 @@ class Object implements Arrayable * * - the class has a setter method associated with the specified name * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVar` is true); + * - the class has a member variable with the specified name (when `$checkVars` is true); * * @param string $name the property name - * @param boolean $checkVar whether to treat member variables as properties + * @param boolean $checkVars whether to treat member variables as properties * @return boolean whether the property can be written * @see canGetProperty */ - public function canSetProperty($name, $checkVar = true) + public function canSetProperty($name, $checkVars = true) { - return method_exists($this, 'set' . $name) || $checkVar && property_exists($this, $name); + return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name); } /** diff --git a/framework/yii/bootstrap/Carousel.php b/framework/yii/bootstrap/Carousel.php index c2c68a7..ec9a0c9 100644 --- a/framework/yii/bootstrap/Carousel.php +++ b/framework/yii/bootstrap/Carousel.php @@ -55,7 +55,7 @@ class Carousel extends Widget * // required, slide content (HTML), such as an image tag * 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>', * // optional, the caption (HTML) of the slide - * 'caption'=> '<h4>This is title</h4><p>This is the caption text</p>', + * 'caption' => '<h4>This is title</h4><p>This is the caption text</p>', * // optional the HTML attributes of the slide container * 'options' => array(), * ) diff --git a/framework/yii/bootstrap/Collapse.php b/framework/yii/bootstrap/Collapse.php index 8aed0b1..77fdde7 100644 --- a/framework/yii/bootstrap/Collapse.php +++ b/framework/yii/bootstrap/Collapse.php @@ -23,7 +23,7 @@ use yii\helpers\Html; * 'Collapsible Group Item #1' => array( * 'content' => 'Anim pariatur cliche...', * // open its content by default - * 'contentOptions' => array('class'=>'in') + * 'contentOptions' => array('class' => 'in') * ), * // another group item * 'Collapsible Group Item #2' => array( @@ -51,9 +51,9 @@ class Collapse extends Widget * // required, the content (HTML) of the group * 'content' => 'Anim pariatur cliche...', * // optional the HTML attributes of the content group - * 'contentOptions'=> array(), + * 'contentOptions' => array(), * // optional the HTML attributes of the group - * 'options'=> array(), + * 'options' => array(), * ) * ``` */ diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index cea83d8..5b5d40b 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -136,6 +136,7 @@ class Nav extends Widget if (is_array($items)) { $items = Dropdown::widget(array( 'items' => $items, + 'encodeLabels' => $this->encodeLabels, 'clientOptions' => false, )); } diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php index f801df5..52804c4 100644 --- a/framework/yii/bootstrap/NavBar.php +++ b/framework/yii/bootstrap/NavBar.php @@ -87,7 +87,7 @@ class NavBar extends Widget * // optional, the menu item class type of the widget to render. Defaults to "Nav" widget. * 'class' => 'Menu item class type', * // required, the configuration options of the widget. - * 'options'=> array(...), + * 'options' => array(...), * ), * // optionally, you can pass a string * '<form class="navbar-search pull-left" action="">' . diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php index 57046b1..33525b0 100644 --- a/framework/yii/bootstrap/Progress.php +++ b/framework/yii/bootstrap/Progress.php @@ -47,7 +47,7 @@ use yii\helpers\Html; * echo Progress::widget(array( * 'bars' => array( * array('percent' => 30, 'options' => array('class' => 'bar-danger')), - * array('percent' => 30, 'label'=>'test', 'options' => array('class' => 'bar-success')), + * array('percent' => 30, 'label' => 'test', 'options' => array('class' => 'bar-success')), * array('percent' => 35, 'options' => array('class' => 'bar-warning')) * ) * )); diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 726dfb0..d6ddd7b 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -28,7 +28,7 @@ use yii\helpers\Html; * 'label' => 'Two', * 'content' => 'Anim pariatur cliche...', * 'headerOptions' => array(...), - * 'options' => array('id'=>'myveryownID'), + * 'options' => array('id' => 'myveryownID'), * ), * array( * 'label' => 'Dropdown', diff --git a/framework/yii/widgets/Captcha.php b/framework/yii/captcha/Captcha.php similarity index 98% rename from framework/yii/widgets/Captcha.php rename to framework/yii/captcha/Captcha.php index 1c538fb..78a4db3 100644 --- a/framework/yii/widgets/Captcha.php +++ b/framework/yii/captcha/Captcha.php @@ -5,13 +5,14 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\widgets; +namespace yii\captcha; use Yii; use yii\base\InvalidConfigException; use yii\helpers\Html; use yii\helpers\Json; -use yii\web\CaptchaAction; +use yii\widgets\InputWidget; + /** * Captcha renders a CAPTCHA image and an input field that takes user-entered verification code. diff --git a/framework/yii/web/CaptchaAction.php b/framework/yii/captcha/CaptchaAction.php similarity index 93% rename from framework/yii/web/CaptchaAction.php rename to framework/yii/captcha/CaptchaAction.php index 98599d9..771cc02 100644 --- a/framework/yii/web/CaptchaAction.php +++ b/framework/yii/captcha/CaptchaAction.php @@ -5,12 +5,11 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\web; +namespace yii\captcha; use Yii; use yii\base\Action; use yii\base\InvalidConfigException; -use yii\widgets\Captcha; /** * CaptchaAction renders a CAPTCHA image. @@ -85,7 +84,7 @@ class CaptchaAction extends Action /** * @var string the TrueType font file. This can be either a file path or path alias. */ - public $fontFile = '@yii/web/SpicyRice.ttf'; + public $fontFile = '@yii/captcha/SpicyRice.ttf'; /** * @var string the fixed verification code. When this property is set, * [[getVerifyCode()]] will always return the value of this property. @@ -116,12 +115,14 @@ class CaptchaAction extends Action if (isset($_GET[self::REFRESH_GET_VAR])) { // AJAX request for regenerating code $code = $this->getVerifyCode(true); + /** @var \yii\web\Controller $controller */ + $controller = $this->controller; return json_encode(array( 'hash1' => $this->generateValidationHash($code), 'hash2' => $this->generateValidationHash(strtolower($code)), // we add a random 'v' parameter so that FireFox can refresh the image // when src attribute of image tag is changed - 'url' => $this->controller->createUrl($this->id, array('v' => uniqid())), + 'url' => $controller->createUrl($this->id, array('v' => uniqid())), )); } else { $this->setHttpHeaders(); @@ -153,7 +154,7 @@ class CaptchaAction extends Action return $this->fixedVerifyCode; } - $session = Yii::$app->session; + $session = Yii::$app->getSession(); $session->open(); $name = $this->getSessionKey(); if ($session[$name] === null || $regenerate) { @@ -189,15 +190,15 @@ class CaptchaAction extends Action */ protected function generateVerifyCode() { + if ($this->minLength > $this->maxLength) { + $this->maxLength = $this->minLength; + } if ($this->minLength < 3) { $this->minLength = 3; } if ($this->maxLength > 20) { $this->maxLength = 20; } - if ($this->minLength > $this->maxLength) { - $this->maxLength = $this->minLength; - } $length = mt_rand($this->minLength, $this->maxLength); $letters = 'bcdfghjklmnpqrstvwxyz'; diff --git a/framework/yii/widgets/CaptchaAsset.php b/framework/yii/captcha/CaptchaAsset.php similarity index 95% rename from framework/yii/widgets/CaptchaAsset.php rename to framework/yii/captcha/CaptchaAsset.php index 4322e8e..50c75b7 100644 --- a/framework/yii/widgets/CaptchaAsset.php +++ b/framework/yii/captcha/CaptchaAsset.php @@ -5,7 +5,8 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\widgets; +namespace yii\captcha; + use yii\web\AssetBundle; /** diff --git a/framework/yii/validators/CaptchaValidator.php b/framework/yii/captcha/CaptchaValidator.php similarity index 97% rename from framework/yii/validators/CaptchaValidator.php rename to framework/yii/captcha/CaptchaValidator.php index 379859c..18c1d0d 100644 --- a/framework/yii/validators/CaptchaValidator.php +++ b/framework/yii/captcha/CaptchaValidator.php @@ -5,11 +5,13 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\validators; +namespace yii\captcha; use Yii; use yii\base\InvalidConfigException; use yii\helpers\Html; +use yii\validators\ValidationAsset; +use yii\validators\Validator; /** * CaptchaValidator validates that the attribute value is the same as the verification code displayed in the CAPTCHA. @@ -74,7 +76,7 @@ class CaptchaValidator extends Validator /** * Returns the CAPTCHA action object. * @throws InvalidConfigException - * @return \yii\web\CaptchaAction the action object + * @return \yii\captcha\CaptchaAction the action object */ public function getCaptchaAction() { diff --git a/framework/yii/web/SpicyRice.md b/framework/yii/captcha/SpicyRice.md similarity index 100% rename from framework/yii/web/SpicyRice.md rename to framework/yii/captcha/SpicyRice.md diff --git a/framework/yii/web/SpicyRice.ttf b/framework/yii/captcha/SpicyRice.ttf similarity index 100% rename from framework/yii/web/SpicyRice.ttf rename to framework/yii/captcha/SpicyRice.ttf Binary files a/framework/yii/web/SpicyRice.ttf and b/framework/yii/captcha/SpicyRice.ttf differ diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 41c9a3a..a638dc0 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -91,6 +91,10 @@ return array( 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php', 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php', 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php', + 'yii\captcha\Captcha' => YII_PATH . '/captcha/Captcha.php', + 'yii\captcha\CaptchaAction' => YII_PATH . '/captcha/CaptchaAction.php', + 'yii\captcha\CaptchaAsset' => YII_PATH . '/captcha/CaptchaAsset.php', + 'yii\captcha\CaptchaValidator' => YII_PATH . '/captcha/CaptchaValidator.php', 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', @@ -167,7 +171,6 @@ return array( 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php', 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php', 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php', - 'yii\validators\CaptchaValidator' => YII_PATH . '/validators/CaptchaValidator.php', 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php', 'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php', 'yii\validators\DefaultValueValidator' => YII_PATH . '/validators/DefaultValueValidator.php', @@ -193,7 +196,6 @@ return array( 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php', 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php', 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php', - 'yii\web\CaptchaAction' => YII_PATH . '/web/CaptchaAction.php', 'yii\web\Controller' => YII_PATH . '/web/Controller.php', 'yii\web\Cookie' => YII_PATH . '/web/Cookie.php', 'yii\web\CookieCollection' => YII_PATH . '/web/CookieCollection.php', @@ -225,14 +227,18 @@ return array( 'yii\widgets\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php', 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php', 'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.php', - 'yii\widgets\Captcha' => YII_PATH . '/widgets/Captcha.php', - 'yii\widgets\CaptchaAsset' => YII_PATH . '/widgets/CaptchaAsset.php', 'yii\widgets\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php', 'yii\widgets\DetailView' => YII_PATH . '/widgets/DetailView.php', 'yii\widgets\FragmentCache' => YII_PATH . '/widgets/FragmentCache.php', + 'yii\widgets\grid\CheckboxColumn' => YII_PATH . '/widgets/grid/CheckboxColumn.php', + 'yii\widgets\grid\Column' => YII_PATH . '/widgets/grid/Column.php', + 'yii\widgets\grid\DataColumn' => YII_PATH . '/widgets/grid/DataColumn.php', + 'yii\widgets\GridView' => YII_PATH . '/widgets/GridView.php', 'yii\widgets\InputWidget' => YII_PATH . '/widgets/InputWidget.php', 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php', - 'yii\widgets\ListPager' => YII_PATH . '/widgets/ListPager.php', + 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php', + 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php', + 'yii\widgets\ListViewBase' => YII_PATH . '/widgets/ListViewBase.php', 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', diff --git a/framework/yii/console/controllers/HelpController.php b/framework/yii/console/controllers/HelpController.php index c1a8e0c..bf97e90 100644 --- a/framework/yii/console/controllers/HelpController.php +++ b/framework/yii/console/controllers/HelpController.php @@ -129,7 +129,7 @@ class HelpController extends Controller $files = scandir($module->getControllerPath()); foreach ($files as $file) { if (strcmp(substr($file, -14), 'Controller.php') === 0) { - $commands[] = $prefix . lcfirst(substr(basename($file), 0, -14)); + $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14)); } } diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index 2b7400c..63635ab 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -29,7 +29,7 @@ use yii\db\Connection; * )); * * // get the posts in the current page - * $posts = $provider->getItems(); + * $posts = $provider->getModels(); * ~~~ * * And the following example shows how to use ActiveDataProvider without ActiveRecord: @@ -44,7 +44,7 @@ use yii\db\Connection; * )); * * // get the posts in the current page - * $posts = $provider->getItems(); + * $posts = $provider->getModels(); * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> @@ -53,18 +53,18 @@ use yii\db\Connection; class ActiveDataProvider extends DataProvider { /** - * @var Query the query that is used to fetch data items and [[totalCount]] + * @var Query the query that is used to fetch data models and [[totalCount]] * if it is not explicitly set. */ public $query; /** - * @var string|callable the column that is used as the key of the data items. - * This can be either a column name, or a callable that returns the key value of a given data item. + * @var string|callable the column that is used as the key of the data models. + * This can be either a column name, or a callable that returns the key value of a given data model. * - * If this is not set, the following rules will be used to determine the keys of the data items: + * If this is not set, the following rules will be used to determine the keys of the data models: * * - If [[query]] is an [[ActiveQuery]] instance, the primary keys of [[ActiveQuery::modelClass]] will be used. - * - Otherwise, the keys of the [[items]] array will be used. + * - Otherwise, the keys of the [[models]] array will be used. * * @see getKeys() */ @@ -75,9 +75,9 @@ class ActiveDataProvider extends DataProvider */ public $db; - private $_items; + private $_models; private $_keys; - private $_count; + private $_totalCount; /** * Initializes the DbCache component. @@ -96,59 +96,55 @@ class ActiveDataProvider extends DataProvider } /** - * Returns the number of data items in the current page. - * This is equivalent to `count($provider->items)`. + * Returns the number of data models in the current page. + * This is equivalent to `count($provider->models)`. * When [[pagination]] is false, this is the same as [[totalCount]]. - * @param boolean $refresh whether to recalculate the item count. If true, - * this will cause re-fetching of [[items]]. - * @return integer the number of data items in the current page. + * @return integer the number of data models in the current page. */ - public function getCount($refresh = false) + public function getCount() { - return count($this->getItems($refresh)); + return count($this->getModels()); } /** - * Returns the total number of data items. + * Returns the total number of data models. * When [[pagination]] is false, this returns the same value as [[count]]. * If [[totalCount]] is not explicitly set, it will be calculated * using [[query]] with a COUNT query. - * @param boolean $refresh whether to recalculate the item count - * @return integer total number of possible data items. + * @return integer total number of possible data models. * @throws InvalidConfigException */ - public function getTotalCount($refresh = false) + public function getTotalCount() { if ($this->getPagination() === false) { - return $this->getCount($refresh); - } elseif ($this->_count === null || $refresh) { + return $this->getCount(); + } elseif ($this->_totalCount === null) { if (!$this->query instanceof Query) { throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); } $query = clone $this->query; - $this->_count = $query->limit(-1)->offset(-1)->count('*', $this->db); + $this->_totalCount = $query->limit(-1)->offset(-1)->count('*', $this->db); } - return $this->_count; + return $this->_totalCount; } /** - * Sets the total number of data items. - * @param integer $value the total number of data items. + * Sets the total number of data models. + * @param integer $value the total number of data models. */ public function setTotalCount($value) { - $this->_count = $value; + $this->_totalCount = $value; } /** - * Returns the data items in the current page. - * @param boolean $refresh whether to re-fetch the data items. - * @return array the list of data items in the current page. - * @throws InvalidConfigException + * Returns the data models in the current page. + * @return array the list of data models in the current page. + * @throws InvalidConfigException if [[query]] is not set or invalid. */ - public function getItems($refresh = false) + public function getModels() { - if ($this->_items === null || $refresh) { + if ($this->_models === null) { if (!$this->query instanceof Query) { throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); } @@ -159,28 +155,27 @@ class ActiveDataProvider extends DataProvider if (($sort = $this->getSort()) !== false) { $this->query->orderBy($sort->getOrders()); } - $this->_items = $this->query->all($this->db); + $this->_models = $this->query->all($this->db); } - return $this->_items; + return $this->_models; } /** - * Returns the key values associated with the data items. - * @param boolean $refresh whether to re-fetch the data items and re-calculate the keys - * @return array the list of key values corresponding to [[items]]. Each data item in [[items]] + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] * is uniquely identified by the corresponding key value in this array. */ - public function getKeys($refresh = false) + public function getKeys() { - if ($this->_keys === null || $refresh) { + if ($this->_keys === null) { $this->_keys = array(); - $items = $this->getItems($refresh); + $models = $this->getModels(); if ($this->key !== null) { - foreach ($items as $item) { + foreach ($models as $model) { if (is_string($this->key)) { - $this->_keys[] = $item[$this->key]; + $this->_keys[] = $model[$this->key]; } else { - $this->_keys[] = call_user_func($this->key, $item); + $this->_keys[] = call_user_func($this->key, $model); } } } elseif ($this->query instanceof ActiveQuery) { @@ -189,22 +184,34 @@ class ActiveDataProvider extends DataProvider $pks = $class::primaryKey(); if (count($pks) === 1) { $pk = $pks[0]; - foreach ($items as $item) { - $this->_keys[] = $item[$pk]; + foreach ($models as $model) { + $this->_keys[] = $model[$pk]; } } else { - foreach ($items as $item) { + foreach ($models as $model) { $keys = array(); foreach ($pks as $pk) { - $keys[] = $item[$pk]; + $keys[] = $model[$pk]; } $this->_keys[] = json_encode($keys); } } } else { - $this->_keys = array_keys($items); + $this->_keys = array_keys($models); } } return $this->_keys; } + + /** + * Refreshes the data provider. + * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, + * they will re-execute the query and return the latest data available. + */ + public function refresh() + { + $this->_models = null; + $this->_totalCount = null; + $this->_keys = null; + } } diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index 6a4aa7a..530bc69 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -13,25 +13,25 @@ use yii\helpers\ArrayHelper; /** * ArrayDataProvider implements a data provider based on a data array. * - * The [[allItems]] property contains all data items that may be sorted and/or paginated. + * The [[allModels]] property contains all data models that may be sorted and/or paginated. * ArrayDataProvider will provide the data after sorting and/or pagination. * You may configure the [[sort]] and [[pagination]] properties to * customize the sorting and pagination behaviors. * - * Elements in the [[allItems]] array may be either objects (e.g. model objects) + * Elements in the [[allModels]] array may be either objects (e.g. model objects) * or associative arrays (e.g. query results of DAO). * Make sure to set the [[key]] property to the name of the field that uniquely * identifies a data record or false if you do not have such a field. * * Compared to [[ActiveDataProvider]], ArrayDataProvider could be less efficient - * because it needs to have [[allItems]] ready. + * because it needs to have [[allModels]] ready. * * ArrayDataProvider may be used in the following way: * * ~~~ * $query = new Query; * $provider = new ArrayDataProvider(array( - * 'allItems' => $query->from('tbl_post')->all(), + * 'allModels' => $query->from('tbl_post')->all(), * 'sort' => array( * 'attributes' => array( * 'id', 'username', 'email', @@ -42,7 +42,7 @@ use yii\helpers\ArrayHelper; * ), * )); * // get the posts in the current page - * $posts = $provider->getItems(); + * $posts = $provider->getModels(); * ~~~ * * Note: if you want to use the sorting feature, you must configure the [[sort]] property @@ -54,116 +54,110 @@ use yii\helpers\ArrayHelper; class ArrayDataProvider extends DataProvider { /** - * @var string|callable the column that is used as the key of the data items. - * This can be either a column name, or a callable that returns the key value of a given data item. - * If this is not set, the index of the [[items]] array will be used. + * @var string|callable the column that is used as the key of the data models. + * This can be either a column name, or a callable that returns the key value of a given data model. + * If this is not set, the index of the [[models]] array will be used. * @see getKeys() */ public $key; /** * @var array the data that is not paginated or sorted. When pagination is enabled, - * this property usually contains more elements than [[items]]. + * this property usually contains more elements than [[models]]. * The array elements must use zero-based integer keys. */ - public $allItems; + public $allModels; private $_totalCount; /** - * Returns the total number of data items. - * @return integer total number of possible data items. - * @throws InvalidConfigException + * Returns the total number of data models. + * @return integer total number of possible data models. */ public function getTotalCount() { if ($this->getPagination() === false) { return $this->getCount(); } elseif ($this->_totalCount === null) { - if ($this->allItems !== null) { - $this->_totalCount = count($this->allItems); - } else { - throw new InvalidConfigException('Unable to determine total item count: either "allItems" or "totalCount" must be set.'); - } + $this->_totalCount = count($this->allModels); } return $this->_totalCount; } /** - * Sets the total number of data items. - * @param integer $value the total number of data items. + * Sets the total number of data models. + * @param integer $value the total number of data models. */ public function setTotalCount($value) { $this->_totalCount = $value; } - private $_items; + private $_models; /** - * Returns the data items in the current page. - * @return array the list of data items in the current page. - * @throws InvalidConfigException + * Returns the data models in the current page. + * @return array the list of data models in the current page. */ - public function getItems() + public function getModels() { - if ($this->_items === null) { - if (($items = $this->allItems) === null) { - throw new InvalidConfigException('Either "items" or "allItems" must be set.'); + if ($this->_models === null) { + if (($models = $this->allModels) === null) { + return array(); } if (($sort = $this->getSort()) !== false) { - $items = $this->sortItems($items, $sort); + $models = $this->sortModels($models, $sort); } if (($pagination = $this->getPagination()) !== false) { $pagination->totalCount = $this->getTotalCount(); - $items = array_slice($items, $pagination->getOffset(), $pagination->getLimit()); + $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } - $this->_items = $items; + $this->_models = $models; } - return $this->_items; + return $this->_models; } /** - * Sets the data items in the current page. - * @param array $items the items in the current page + * Sets the data models in the current page. + * @param array $models the models in the current page */ - public function setItems($items) + public function setModels($models) { - $this->_items = $items; + $this->_models = $models; } private $_keys; /** - * Returns the key values associated with the data items. - * @return array the list of key values corresponding to [[items]]. Each data item in [[items]] + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] * is uniquely identified by the corresponding key value in this array. */ public function getKeys() { if ($this->_keys === null) { $this->_keys = array(); - $items = $this->getItems(); + $models = $this->getModels(); if ($this->key !== null) { - foreach ($items as $item) { + foreach ($models as $model) { if (is_string($this->key)) { - $this->_keys[] = $item[$this->key]; + $this->_keys[] = $model[$this->key]; } else { - $this->_keys[] = call_user_func($this->key, $item); + $this->_keys[] = call_user_func($this->key, $model); } } } else { - $this->_keys = array_keys($items); + $this->_keys = array_keys($models); } } return $this->_keys; } /** - * Sets the key values associated with the data items. - * @param array $keys the list of key values corresponding to [[items]]. + * Sets the key values associated with the data models. + * @param array $keys the list of key values corresponding to [[models]]. */ public function setKeys($keys) { @@ -171,17 +165,17 @@ class ArrayDataProvider extends DataProvider } /** - * Sorts the data items according to the given sort definition - * @param array $items the items to be sorted + * Sorts the data models according to the given sort definition + * @param array $models the models to be sorted * @param Sort $sort the sort definition - * @return array the sorted data items + * @return array the sorted data models */ - protected function sortItems($items, $sort) + protected function sortModels($models, $sort) { $orders = $sort->getOrders(); if (!empty($orders)) { - ArrayHelper::multisort($items, array_keys($orders), array_values($orders)); + ArrayHelper::multisort($models, array_keys($orders), array_values($orders)); } - return $items; + return $models; } } diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php index 1d4b785..5f36f7e 100644 --- a/framework/yii/data/DataProvider.php +++ b/framework/yii/data/DataProvider.php @@ -118,11 +118,11 @@ abstract class DataProvider extends Component implements IDataProvider } /** - * Returns the number of data items in the current page. - * @return integer the number of data items in the current page. + * Returns the number of data models in the current page. + * @return integer the number of data models in the current page. */ public function getCount() { - return count($this->getItems()); + return count($this->getModels()); } } diff --git a/framework/yii/data/IDataProvider.php b/framework/yii/data/IDataProvider.php index 7b6dc93..9ae5546 100644 --- a/framework/yii/data/IDataProvider.php +++ b/framework/yii/data/IDataProvider.php @@ -19,29 +19,29 @@ namespace yii\data; interface IDataProvider { /** - * Returns the number of data items in the current page. - * This is equivalent to `count($provider->getItems())`. + * Returns the number of data models in the current page. + * This is equivalent to `count($provider->getModels())`. * When [[pagination]] is false, this is the same as [[totalCount]]. - * @return integer the number of data items in the current page. + * @return integer the number of data models in the current page. */ public function getCount(); /** - * Returns the total number of data items. + * Returns the total number of data models. * When [[pagination]] is false, this is the same as [[count]]. - * @return integer total number of possible data items. + * @return integer total number of possible data models. */ public function getTotalCount(); /** - * Returns the data items in the current page. - * @return array the list of data items in the current page. + * Returns the data models in the current page. + * @return array the list of data models in the current page. */ - public function getItems(); + public function getModels(); /** - * Returns the key values associated with the data items. - * @return array the list of key values corresponding to [[items]]. Each data item in [[items]] + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] * is uniquely identified by the corresponding key value in this array. */ public function getKeys(); diff --git a/framework/yii/data/Sort.php b/framework/yii/data/Sort.php index cb5dd82..4d7851a 100644 --- a/framework/yii/data/Sort.php +++ b/framework/yii/data/Sort.php @@ -191,17 +191,18 @@ class Sort extends Object { $attributes = array(); foreach ($this->attributes as $name => $attribute) { - if (is_array($attribute)) { - $attributes[$name] = $attribute; - if (!isset($attribute['label'])) { - $attributes[$name]['label'] = Inflector::camel2words($name); - } - } else { + if (!is_array($attribute)) { $attributes[$attribute] = array( 'asc' => array($attribute => self::ASC), 'desc' => array($attribute => self::DESC), 'label' => Inflector::camel2words($attribute), ); + } elseif (!isset($attribute['asc'], $attribute['desc'], $attribute['label'])) { + $attributes[$name] = array_merge(array( + 'asc' => array($name => self::ASC), + 'desc' => array($name => self::DESC), + 'label' => Inflector::camel2words($name), + ), $attribute); } } $this->attributes = $attributes; diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php index e0c40f7..4d08659 100644 --- a/framework/yii/db/ActiveQuery.php +++ b/framework/yii/db/ActiveQuery.php @@ -124,10 +124,14 @@ class ActiveQuery extends Query { $command = $this->createCommand($db); $row = $command->queryOne(); - if ($row !== false && !$this->asArray) { - /** @var $class ActiveRecord */ - $class = $this->modelClass; - $model = $class::create($row); + if ($row !== false) { + if ($this->asArray) { + $model = $row; + } else { + /** @var $class ActiveRecord */ + $class = $this->modelClass; + $model = $class::create($row); + } if (!empty($this->with)) { $models = array($model); $this->populateRelations($models, $this->with); @@ -135,7 +139,7 @@ class ActiveQuery extends Query } return $model; } else { - return $row === false ? null : $row; + return null; } } diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 3ad5bd3..6e42106 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -536,6 +536,16 @@ class ActiveRecord extends Model } /** + * Returns a value indicating whether the model has an attribute with the specified name. + * @param string $name the name of the attribute + * @return boolean whether the model has an attribute with the specified name. + */ + public function hasAttribute($name) + { + return isset($this->_attributes[$name]) || isset($this->getTableSchema()->columns[$name]); + } + + /** * Returns the old attribute values. * @return array the old attribute values (name-value pairs) */ @@ -1400,10 +1410,10 @@ class ActiveRecord extends Model * @param string $class the class name to be namespaced * @return string the namespaced class name */ - protected function getNamespacedClass($class) + protected static function getNamespacedClass($class) { if (strpos($class, '\\') === false) { - $reflector = new \ReflectionClass($this); + $reflector = new \ReflectionClass(static::className()); return $reflector->getNamespaceName() . '\\' . $class; } else { return $class; diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index a754e34..bf93a2c 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -146,9 +146,9 @@ class Command extends \yii\base\Component try { $this->pdoStatement = $this->db->pdo->prepare($sql); } catch (\Exception $e) { - Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __METHOD__); + $message = $e->getMessage() . "\nFailed to prepare SQL: $sql"; $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - throw new Exception($e->getMessage(), $errorInfo, (int)$e->getCode(), $e); + throw new Exception($message, $errorInfo, (int)$e->getCode(), $e); } } } @@ -293,10 +293,7 @@ class Command extends \yii\base\Component return $n; } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); - $message = $e->getMessage(); - - Yii::error("$message\nFailed to execute SQL: $rawSql", __METHOD__); - + $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql"; $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; throw new Exception($message, $errorInfo, (int)$e->getCode(), $e); } @@ -430,8 +427,7 @@ class Command extends \yii\base\Component return $result; } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); - $message = $e->getMessage(); - Yii::error("$message\nCommand::$method() failed: $rawSql", __METHOD__); + $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql"; $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; throw new Exception($message, $errorInfo, (int)$e->getCode(), $e); } diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 0dd47d8..f27698a 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -307,9 +307,7 @@ class Connection extends Component Yii::endProfile($token, __METHOD__); } catch (\PDOException $e) { Yii::endProfile($token, __METHOD__); - Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__); - $message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.'; - throw new Exception($message, $e->errorInfo, (int)$e->getCode(), $e); + throw new Exception($e->getMessage(), $e->errorInfo, (int)$e->getCode(), $e); } } } diff --git a/framework/yii/debug/panels/LogPanel.php b/framework/yii/debug/panels/LogPanel.php index b1ad63d..0f56281 100644 --- a/framework/yii/debug/panels/LogPanel.php +++ b/framework/yii/debug/panels/LogPanel.php @@ -55,7 +55,7 @@ EOD; foreach ($this->data['messages'] as $log) { list ($message, $level, $category, $time, $traces) = $log; $time = date('H:i:s.', $time) . sprintf('%03d', (int)(($time - (int)$time) * 1000)); - $message = Html::encode($message); + $message = nl2br(Html::encode($message)); if (!empty($traces)) { $message .= Html::ul($traces, array( 'class' => 'trace', diff --git a/framework/yii/debug/panels/ProfilingPanel.php b/framework/yii/debug/panels/ProfilingPanel.php index 5bd32ee..b614611 100644 --- a/framework/yii/debug/panels/ProfilingPanel.php +++ b/framework/yii/debug/panels/ProfilingPanel.php @@ -33,10 +33,10 @@ class ProfilingPanel extends Panel return <<<EOD <div class="yii-debug-toolbar-block"> - <a href="$url" title="total processing time">Time: <span class="label">$time</span></a> + <a href="$url" title="Total processing time">Time: <span class="label">$time</span></a> </div> <div class="yii-debug-toolbar-block"> - <a href="$url" title="peak memory consumption">Memory: <span class="label">$memory</span></a> + <a href="$url" title="Peak memory consumption">Memory: <span class="label">$memory</span></a> </div> EOD; } diff --git a/framework/yii/debug/panels/RequestPanel.php b/framework/yii/debug/panels/RequestPanel.php index e709de6..e655f2d 100644 --- a/framework/yii/debug/panels/RequestPanel.php +++ b/framework/yii/debug/panels/RequestPanel.php @@ -12,6 +12,7 @@ use yii\base\InlineAction; use yii\bootstrap\Tabs; use yii\debug\Panel; use yii\helpers\Html; +use yii\web\Response; /** * Debugger panel that collects and displays request data. @@ -29,9 +30,24 @@ class RequestPanel extends Panel public function getSummary() { $url = $this->getUrl(); + $statusCode = $this->data['statusCode']; + if ($statusCode === null) { + $statusCode = 200; + } + if ($statusCode >= 200 && $statusCode < 300) { + $class = 'label-success'; + } elseif ($statusCode >= 100 && $statusCode < 200) { + $class = 'label-info'; + } else { + $class = 'label-important'; + } + $statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Response::$httpStatuses[$statusCode] : ''); return <<<EOD <div class="yii-debug-toolbar-block"> + <a href="$url" title="Status code: $statusCode $statusText"><span class="label $class">$statusCode</span></a> +</div> +<div class="yii-debug-toolbar-block"> <a href="$url">Action: <span class="label">{$this->data['action']}</span></a> </div> EOD; @@ -113,6 +129,7 @@ EOD; $session = Yii::$app->getComponent('session', false); return array( 'flashes' => $session ? $session->getAllFlashes() : array(), + 'statusCode' => Yii::$app->getResponse()->getStatusCode(), 'requestHeaders' => $requestHeaders, 'responseHeaders' => $responseHeaders, 'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute, diff --git a/framework/yii/helpers/HtmlBase.php b/framework/yii/helpers/HtmlBase.php index b1d514f..2c8bead 100644 --- a/framework/yii/helpers/HtmlBase.php +++ b/framework/yii/helpers/HtmlBase.php @@ -1339,11 +1339,12 @@ class HtmlBase * * - is an empty string: the currently requested URL will be returned; * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result - * is an absolute URL, it will be returned with any change further; Otherwise, the result + * is an absolute URL, it will be returned without any change further; Otherwise, the result * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. * - is an array: the first array element is considered a route, while the rest of the name-value * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. * For example: `array('post/index', 'page' => 2)`, `array('index')`. + * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. * * @param array|string $url the parameter to be used to generate a valid URL * @return string the normalized URL @@ -1355,7 +1356,7 @@ class HtmlBase if (isset($url[0])) { $route = $url[0]; $params = array_splice($url, 1); - if (Yii::$app->controller !== null) { + if (Yii::$app->controller instanceof \yii\web\Controller) { return Yii::$app->controller->createUrl($route, $params); } else { return Yii::$app->getUrlManager()->createUrl($route, $params); diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/SecurityBase.php index 9b743c4..05b2682 100644 --- a/framework/yii/helpers/SecurityBase.php +++ b/framework/yii/helpers/SecurityBase.php @@ -120,7 +120,10 @@ class SecurityBase static $keys; $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; if ($keys === null) { - $keys = is_file($keyFile) ? require($keyFile) : array(); + $keys = array(); + if (is_file($keyFile)) { + $keys = require($keyFile); + } } if (!isset($keys[$name])) { $keys[$name] = static::generateRandomKey($length); diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 670544d..005a205 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -41,7 +41,7 @@ return array( array( 'name' => 'Intl extension', 'mandatory' => false, - 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'), + 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='), 'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support', 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator or the <code>yii\i18n\Formatter</code> class.' ), diff --git a/framework/yii/validators/Validator.php b/framework/yii/validators/Validator.php index fe31936..c519706 100644 --- a/framework/yii/validators/Validator.php +++ b/framework/yii/validators/Validator.php @@ -49,7 +49,7 @@ abstract class Validator extends Component */ public static $builtInValidators = array( 'boolean' => 'yii\validators\BooleanValidator', - 'captcha' => 'yii\validators\CaptchaValidator', + 'captcha' => 'yii\captcha\CaptchaValidator', 'compare' => 'yii\validators\CompareValidator', 'date' => 'yii\validators\DateValidator', 'default' => 'yii\validators\DefaultValueValidator', diff --git a/framework/yii/views/errorHandler/callStackItem.php b/framework/yii/views/errorHandler/callStackItem.php index 2cbced0..20ad398 100644 --- a/framework/yii/views/errorHandler/callStackItem.php +++ b/framework/yii/views/errorHandler/callStackItem.php @@ -8,19 +8,19 @@ * @var string[] $lines * @var integer $begin * @var integer $end - * @var \yii\base\ErrorHandler $this + * @var \yii\base\ErrorHandler $handler */ ?> -<li class="<?php if (!$this->isCoreFile($file) || $index === 1) echo 'application'; ?> call-stack-item" +<li class="<?php if (!$handler->isCoreFile($file) || $index === 1) echo 'application'; ?> call-stack-item" data-line="<?php echo (int)($line - $begin); ?>"> <div class="element-wrap"> <div class="element"> <span class="item-number"><?php echo (int)$index; ?>.</span> - <span class="text"><?php if ($file !== null) echo 'in ' . $this->htmlEncode($file); ?></span> + <span class="text"><?php if ($file !== null) echo 'in ' . $handler->htmlEncode($file); ?></span> <?php if ($method !== null): ?> <span class="call"> <?php if ($file !== null) echo '–' ?> - <?php if ($class !== null) echo $this->addTypeLinks($class) . '::'; ?><?php echo $this->addTypeLinks($method . '()'); ?> + <?php if ($class !== null) echo $handler->addTypeLinks($class) . '::'; ?><?php echo $handler->addTypeLinks($method . '()'); ?> </span> <?php endif; ?> <span class="at"><?php if ($line !== null) echo 'at line'; ?></span> @@ -36,7 +36,7 @@ <pre><?php // fill empty lines with a whitespace to avoid rendering problems in opera for ($i = $begin; $i <= $end; ++$i) { - echo (trim($lines[$i]) == '') ? " \n" : $this->htmlEncode($lines[$i]); + echo (trim($lines[$i]) == '') ? " \n" : $handler->htmlEncode($lines[$i]); } ?></pre> </div> diff --git a/framework/yii/views/errorHandler/error.php b/framework/yii/views/errorHandler/error.php index 4765bdd..c9afaf5 100644 --- a/framework/yii/views/errorHandler/error.php +++ b/framework/yii/views/errorHandler/error.php @@ -1,10 +1,11 @@ <?php /** * @var \Exception $exception - * @var \yii\base\ErrorHandler $this + * @var \yii\base\ErrorHandler $handler */ -$title = $this->htmlEncode($exception instanceof \yii\base\Exception ? $exception->getName() : get_class($exception)); +$title = $handler->htmlEncode($exception instanceof \yii\base\Exception ? $exception->getName() : get_class($exception)); ?> +<?php if (method_exists($this, 'beginPage')) $this->beginPage(); ?> <!DOCTYPE html> <html> <head> @@ -50,16 +51,18 @@ $title = $this->htmlEncode($exception instanceof \yii\base\Exception ? $exceptio </head> <body> -<h1><?php echo $title?></h1> -<h2><?php echo nl2br($this->htmlEncode($exception->getMessage()))?></h2> -<p> - The above error occurred while the Web server was processing your request. -</p> -<p> - Please contact us if you think this is a server error. Thank you. -</p> -<div class="version"> - <?php echo date('Y-m-d H:i:s', time())?> -</div> + <h1><?php echo $title?></h1> + <h2><?php echo nl2br($handler->htmlEncode($exception->getMessage()))?></h2> + <p> + The above error occurred while the Web server was processing your request. + </p> + <p> + Please contact us if you think this is a server error. Thank you. + </p> + <div class="version"> + <?php echo date('Y-m-d H:i:s', time())?> + </div> + <?php if (method_exists($this, 'endBody')) $this->endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?> </body> </html> +<?php if (method_exists($this, 'endPage')) $this->endPage(); ?> diff --git a/framework/yii/views/errorHandler/exception.php b/framework/yii/views/errorHandler/exception.php index 8c6612a..2062277 100644 --- a/framework/yii/views/errorHandler/exception.php +++ b/framework/yii/views/errorHandler/exception.php @@ -1,9 +1,10 @@ <?php /** * @var \Exception $exception - * @var \yii\base\ErrorHandler $this + * @var \yii\base\ErrorHandler $handler */ ?> +<?php if (method_exists($this, 'beginPage')) $this->beginPage(); ?> <!doctype html> <html lang="en-us"> @@ -12,11 +13,11 @@ <title><?php if ($exception instanceof \yii\web\HttpException) { - echo (int) $exception->statusCode . ' ' . $this->htmlEncode($exception->getName()); + echo (int) $exception->statusCode . ' ' . $handler->htmlEncode($exception->getName()); } elseif ($exception instanceof \yii\base\Exception) { - echo $this->htmlEncode($exception->getName() . ' – ' . get_class($exception)); + echo $handler->htmlEncode($exception->getName() . ' – ' . get_class($exception)); } else { - echo $this->htmlEncode(get_class($exception)); + echo $handler->htmlEncode(get_class($exception)); } ?></title> @@ -38,14 +39,6 @@ ul{ } /* base */ -::selection{ - color: #fff !important; - background-color: #e51717 !important; -} -::-moz-selection{ - color: #fff !important; - background-color: #e51717 !important; -} a{ text-decoration: none; } @@ -353,32 +346,32 @@ pre .diff .change{ <?php if ($exception instanceof \yii\base\ErrorException): ?> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEwAAABACAIAAAA/NwYHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjU4QjIxQTgwQzU1MDExRTJBNEMxRURGMUEzMjA1M0UxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjU4QjIxQTgxQzU1MDExRTJBNEMxRURGMUEzMjA1M0UxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NThCMjFBN0VDNTUwMTFFMkE0QzFFREYxQTMyMDUzRTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NThCMjFBN0ZDNTUwMTFFMkE0QzFFREYxQTMyMDUzRTEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz74UrDRAAAOWUlEQVR42txb2W8T1xf2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" alt="Gears"/> <h1> - <span><?php echo $this->htmlEncode($exception->getName()); ?></span> - – <?php echo $this->addTypeLinks(get_class($exception)); ?> + <span><?php echo $handler->htmlEncode($exception->getName()); ?></span> + – <?php echo $handler->addTypeLinks(get_class($exception)); ?> </h1> <?php else: ?> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA6CAIAAACPssguAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjcwRURBOUQxQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjcwRURBOUQyQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NzBFREE5Q0ZDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzBFREE5RDBDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4L6mYsAAAE9klEQVR42tRa227aTBDGiwkhJtD8EanSQ6RKqKpSBbWqql5U7QP0TfsSvWhvStMEO6cqDUpjRylSQ1QC+IQN/mdrhBIb413vgsheIDDr9XwzOzPfzFrwPC/Fe8Cauiybe3uersNPQZJylYr04oUgCNyfJXJfcTAYXH/5YoH0qjq66LbbTrtdfPcOITTXALDuf/40FSWlabeuq6rpeZkHD6Ryma8dOOvDdV19Zycg/XBoml6r9ft9vk9EnNV/dOT8+RM1oXdxof/4wdfrEF/1G7Va+uIiagL8BfaBafMIAHy38/17/+/fydNgAkyDyXMHwLEsS1EmqH9kBOvgACbPFwDQaPfr11S9TjT7+JijEfgAsFst8/CQYv7BQe/6el4AQGTsfPuGbqSt2CGcnrarVS4hlRUAxETz9+/eyQntjXAL3MgeUhG7+vXtbSr1Dx+sqoYss3sCYlS/cXLinJ8nu713emqoKqMRmAD4xAElBQA3GpA62DwBsagfiIN7ecnyeHZyIbKoP4o46KurY2+Rrq7CeQ08YenZs0wmM1MAI+KQDv3VKpd7r1+L2WwQsGH0q9VCyN3dZrOrKPdevUpGsxMCmEAcMqVSaWNjcXExcN00zRbQ7BAAWMSs1ZY2N7NLSzMCMJk4ZBFaXl4OAxBF0Yza68fH3Z2dzNu3Ceo1lMB3raurScQhgqjBDhGiibS1v98zzVlEIax+CJ0TM9fYqAIXB9HRBsiFXq0myGuIVv2xxGGQNCaCVcG2tCGVDkBi4jBU80ThYFlwLVojICr1kxAHFLHRsWoNgzu5oABASBw8247ygVScZJhcyDIVuUDk6tf39oiIg+exUAOwMNiZfAVSAI7jGPv7sSUvhwLl/Jyqc0EEwA+dsR2H+EFWy4OdwdqERiACQNhxGIaaaCcWyDY3Zni7u2BzPgDoOg64Yo92YvJ43e2CzUlCKmIlDsSZ2FcG4Qq4faQoJO0jRKJ+uszFq+tWr5PkNRRPHM7O2IWBpdxGYxrkAk2DODihbAqKdJpNkRIDJhdxniDGEgdavgpSWp8+XX7+HADg9vsJCnDcPnr+XHr0KKpeE6fScdA0l1P7AIwAu2BxfR3qIYotREEcpj8mkwsxkjjs7iYmDuRdCXJykSuXx3YuxEji0O2mE0lvlEpWpZK6vWsxv2s0BCjeE1kVk4ujo2KlEvYEkZE4jMlBCK1sbUFdf7NCx22YlRVHURKuCeSiVpM2N8NGEFmJwzgAIH2hUAhYAD6vGQ6J/bOpe2/eBDoXiJ04jNm1CAVsDT8Zj7ijyAViJQ6zHPV6Z3s7kNfQrcylqlCVpuZ42IeHgbMpdJM4QD2auFc+mxE+m0JUHQfSqHd2Fsg7mMxxWjxwNiX43yBzNT9+9GSZj6I2NsKZH5e5Y9+hSODQ79+vfvjgP0K8SRzSvCw9jgvxJBe/fpmaln/yBAe3WXYcOHYuRmdTCFctmsah4zBjhtdo+D08BGHVhs1zd9Q/MgKIDcJjCwzyefv+/dRdG86/zItzvri+7knS3ZLefPgQPX7sMxSUy+W8ly+NtbW7Ir2+tjaoVHL5fDqdFgFENpstlMtQ/bdlOc3pJZLpjX6x6G1t/ff0KegdH1v96yV7EEk7nU6r1bJtm+PrVPx9FyFQd7FYBMa+sLAwBODnMgirAAM+p/EqLDcuJAiwbaCsgU+fsf8vwADjle3ME1OGEwAAAABJRU5ErkJggg==" alt="Attention"/> <h1><?php if ($exception instanceof \yii\web\HttpException) { - echo '<span>' . $this->createHttpStatusLink($exception->statusCode, $this->htmlEncode($exception->getName())) . '</span>'; - echo ' – ' . $this->addTypeLinks(get_class($exception)); + echo '<span>' . $handler->createHttpStatusLink($exception->statusCode, $handler->htmlEncode($exception->getName())) . '</span>'; + echo ' – ' . $handler->addTypeLinks(get_class($exception)); } elseif ($exception instanceof \yii\base\Exception) { - echo '<span>' . $this->htmlEncode($exception->getName()) . '</span>'; - echo ' – ' . $this->addTypeLinks(get_class($exception)); + echo '<span>' . $handler->htmlEncode($exception->getName()) . '</span>'; + echo ' – ' . $handler->addTypeLinks(get_class($exception)); } else { - echo '<span>' . $this->htmlEncode(get_class($exception)) . '</span>'; + echo '<span>' . $handler->htmlEncode(get_class($exception)) . '</span>'; } ?></h1> <?php endif; ?> - <h2><?php echo $this->htmlEncode($exception->getMessage()); ?></h2> - <?php echo $this->renderPreviousExceptions($exception); ?> + <h2><?php echo $handler->htmlEncode($exception->getMessage()); ?></h2> + <?php echo $handler->renderPreviousExceptions($exception); ?> </div> <div class="call-stack"> <ul> - <?php echo $this->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, 1); ?> + <?php echo $handler->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, 1); ?> <?php for ($i = 1, $trace = $exception->getTrace(), $length = count($trace); $i < $length; ++$i): ?> - <?php echo $this->renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, + <?php echo $handler->renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, $i + 1); ?> <?php endfor; ?> </ul> @@ -386,15 +379,15 @@ pre .diff .change{ <div class="request"> <div class="code"> - <?php echo $this->renderRequest(); ?> + <?php echo $handler->renderRequest(); ?> </div> </div> <div class="footer"> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAS4AAADeCAIAAAAMx3q5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkE5NkEyOTAzQzQ3RDExRTI4NjIzOEE2RkU5QTc4RTU0IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkE5NkEyOTA0QzQ3RDExRTI4NjIzOEE2RkU5QTc4RTU0Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTk2QTI5MDFDNDdEMTFFMjg2MjM4QTZGRTlBNzhFNTQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTk2QTI5MDJDNDdEMTFFMjg2MjM4QTZGRTlBNzhFNTQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4DYCAjAAAx4UlEQVR42ux9aZvjuJEmgqTOvCrrrmr3jO3xjD37/3/FftiP3me965nxbl/VVdWVl1IXyVhSJ0EEgABJSZQEtLoyUwJBisSLN24AIgohlv+W2vLN6f/47/kfkP0nlr9s/6V/gfJA8hsAxJvEgUQH5Q/QdgD+UeQ75BexXk/2I+oGly/h+g0EkWA0sJ6l9c0wee6m/xjPv9hHaOpKTMMhsq8Dqwyf/9OLrt5d/EX3ZM3POjLfSjGbCd+cZkM8S+5+gcfPwdUbDiCX9/l4AWlaxJP7cfw1X9HQAkAor4VYDaVFtigfiNtbjLpR1z0AyVPDaiDy2hbvTpPHBGchdJc3ofRY1XeKLbDc6Nk0O3b5EuRrex2Lv1Vmgy3HSOOU+AeAPIp6B1bPDrT3E/hHgYafDdgAamT5I0yT5P6X5Me/pnc/YxpXm9BHjUMUyd3kv2zAAWF4kPrnw4QlVB7TMsVM7W7yY7WbGZzSzGhdS5M0A+RPf03v7YDERTsZYfVu8o9UJAToOC8FpcVlvAIglSFhQw1MQAK1gpDXM5p/wfUXVx+o4REHDsSvuxNg1LjMfQAsKiKTD4ufgMKM0AQfqsOWnhVoZtQGkI+/WsF2RIA0XGcmmk6Su7NdgZ9mXyocFXgC3B9Dfvsx+fmv6fM9B5BHLZo+TH+AClxIiq5QXEm3XASyxsOnRygvpFB8GY6kTlS8nm2Hx9mnCg83sHc1kIkkQQiTlqiyEJgUS4bGqOdDIZsDgKcrWPlQpxmSCipxyOIVz9Ov/5l+/jvOxma8HZ28urna0fzXBKfnvOrG6XSaPLquqgF3eLNqXV1Brteg4mcEDpsYhjtrp0/Jp/+Fdz9hmlgBeVyiaYKzp9nPlR+mhTAJniSp0vLQCIYsTGXtCEUuJrRHab6MjDIqeQMtUITBkJ6Oxe9h4EPZviwqA0dPoTqxwY0PWVfF40OD4i93SJ8+J7/8T3z+dgL0uLnCyjjcwVp8iHHWgz3Hd67rqY0Vw5ASR2t/T3DssMNnyceh8dqqXXCuQP6/9MvfMZ4dCz0armSWPo3j3ypriE6EKUxUCWbfGT2yk4mVWO2l4xHj0fyrm9mm4eUE7JqXXYIAmwpnZemafAgWHKq2dp34Y5sDS3l1lP76N3z8ZCbA1tLjQSgRGu/Y5PqfjzSef3Na0aJ937D6fNiYTALO38HOh8C6VOXUudL48AnGD8Ht99jpG2IyzBEbB6TEefo0T56g4hTB1f+CDmUxPxjUDrn9uxiBg9arKYywJkbqmy/XZU1ozsZyw3xqQZNIO8gMoS26VS/MHKOx64k+Hyef/4ajL1Z6bCEljue/NbpyO0iwoNfTTQul+8rtdHyKyTh2cK7aoQiX18R0N7jmtZdsM24RrggwG4QAbBhy8luYjS6ksQw0diRdcJ9O3i4ckt7/lH79D5HM2yasGs6Y4mycfBUVnImEw5DUDFlAADsAy2qkaSZSegiw9MZVm8aPhrtXeic42HLKUaaO0RbXSJs+pZ//JiYPLbflFHyJn9sxaZx40+VkZjRqJtE4/tYoK4YhtVZo74+uV1n6MH1pOx/S4swu+LD0KHXeLsPD5gYPFO9eHlGefvsHPvxs9j3uDY3mE03irzuFX+Xo0/orMDier9g/TmcJzpqD4tK16NtBaGf0Jf36d6uwelhKnMS/oUigjhOD1w+EKeCXRYfUSmqNC5fZxejnUEKeJwUZ1fzs3KNtrCI2Jweq0mpGRXszAuisfAh2PtQMa8ul0V0bkHpv3ohD5pP0y/8W04cDqo7mwafJvV80tTcnfmDeRrszAy6u/A09cEuT9Ld/wNU7cflW6POM9+nn2MyqFGcLKDqfVz6ACi7XLdyojeBCxY9Bng43C2WhL5ivRu/noBOR136OokvD3BhQDCNt9FqBKukuVu+82dYMBh4FniAPPOcEcSZjyJ7u7ODkCwHdXaL64dMnEY/h5nsUQRvQ6CmR05J0tpDeQ68rnpbqOHlIf/sPEU/2qTqaB5ylT03ZZsjkKHOii8EdYlAjNQlTW9XAoD1KfaU0K+2ZJjyXBktXhG6vrCwBn0A0zGDNCa4f3aa7n6CpPcWKYtednXp8lPJs0QxNNoe16rh3NOrGz9b7aexZ0dLmyTOnGw+KnZ7pU/cAsr00l8uqH+ENTr3q3Ys0Tb/8HzG+M1hr9mNWXUa61Xk5EaaWJ4U9YMDMkKSlrZJllThgnjYIxdxyA25z0bzes6cy7aVkRnu7n5UVDQQVgQhWfVJLhoQNP334AUe/GlDXCBr3Ip3ufLndI01AZVaMMjHJ+sxydbFOZGazQeF1SJgZkQgufMaKGXCz0HBx8vSrSOZw/Z3OWrMLK05xtmSsWHMWy8UJyV/NByFlGi29tVrMC+HmtIkVS2dAajDN1RQsq3l8RrFnzPPyB5TAqXBFf0gTBxiyhJi3H3TTFoxGR8vZgO27ZMU4QhUSVTsBMC+KVk2pDjj+hg8/7pobdS3G8aHpDpiPTujz7rVjATfThlxxi51LWRrkM+JVsO728hziJGHdL2j0IYC7VsqjKfpDNz50+foAO5mKCzSKeAK3f9A5OXbk4ZivpFPzyFj5S4NytJUqgaxlXEimkinO4oSU3Y9IkCd5CEg/Vto92mvhcqNtgv5wV6vfAeOwYRfQcP+OdarnLisgx2P89p8C02a50ZxMEKdjMKZTwD4fEFPZ3+LU8YYAOChPCjfO03FjUCzH3ICmtKk1JttM//zOTrYiXb22ynFthog21Z7HqmSukUgtctn2PBhP8K55NBqBmjKQAeRrx+s2cCxwsPuFoXCvkp1BsdrlQY3VsXL9m8bMrU3PnaYL0ON8r2ic1zCfOuATKoWBK6548u4BcQL9nS7gl5vruP4xYxhR2QLqxTVh7CAyfdlmSWOuLwjHbEYnPrSjwp0PVWoE4ZbQbA8u16ZpHQqNTS9c1Qlzp9pD40Kztvgic/epwMeFH0Pbm6Q6341TkV+u22ZdLtOZIVVqqXEvrD5gMZQX9AI+N06TJ6ta7pDFD8Mr0zLCKiJe5E89H1TIgXIXTbUFxU1xbdaEZlv9eKvvnvFgdVNzO3kzNN7/V000Om294lv95gDF4PrW36+jafMxPvxfsUt/IzT3ctIqDddhHFNT1QbIyDUONwpHbmwOitAfQhhqItGqxrhVsJpalTdhiTU31S91ivMm+JCvMdjmjpoCQKqWOmtr9sH8GZ9+2hEaN/uW+cZscTplQZGpLsLVbXOmwEoSpvNR0Gi3pi6VGf5W+6omdztCY5JONH4Kw4tZKcONJ4W7vE+zGS8EqzI3pjhvjBXz3lcv6mqJ5uh4p5hve8Q58AQG/W038q3Eh1YZzn46E2Wro9FMqCZeTe7E5DdxoMi4ystVK/fC2O3p3KqD1yuuYcu7b5ZYBLOaziH4EOoO6nROfPoFIBS9G37UOAef1aogoul4qc420B8TJFnogOr1ATH29hPYFiYv9JYix4n4ODkyTpQuu9ra5saKEEZwfbsrLdHwmdN+lrtfGCuF0dhwaFMLNXKGvtJxgUjx6UeRTBvkxii42BVvHCIQEhroAjW/QWBQDmnv4s0rEw4bID1w/OIu3aDRiB9wl7mg+jShJVL2pefuDR4ady2y6jVDIIxR2nBCrRrpmO5DaS3a/fvMeqMJjWgjS+fq4MHVrQgid9IDwdeM7DgE13lviWG1xu7U2nXDVgHEVhhSQ7FMBXXdDdPchLMIHDWg8SDOQzBpxs5LMVTJGbJ47bnlF6A6N1Yp1E8bb5wojF84nDkF1aLqfEmoctrEToUrqL8YKZceT5YG1WYuEMJdAJJWTvSb/doYkrsqltgNKDuZnRvZooq65FWC4st34swb1OTJ3ShFnHjK+aMYf2lECo1gsAchlq2CawHZ4MpYgRsrsiLXu9gfQv/CQYQDHu/ZnfXOkxEMqgCnkLluzeQalqzhb1oXm76ivOEu2dx2y/V4/FnMn+qjMYDuYRY9fciNExphj0LMrgTU/LDbt8K3o225mJrOa6IxgA64u/mhajmwsgWHWpX08od8aipWTiumsvV3VXx2+r4VoQiZuhiEDIONtjwImdZlUyl5tAmOBVeLD0szTkGpZ/tVAPTXpteJDL57gxXSHMCiXltuwvnBbMKxthD6flFrsAVWiZSWUcNI8mr4dnQtnornT7V0xWBQLfpbpkhntlRWofKq52bLMQXKSYUS+JFx5DreC692woqS8YZZUMO1ggsnxs0a813b/ShHt3GMt0CrJwb+V6dCSaazqIVWvxsQdsiMGmf3dZTGTFfMZFS/pu2KFTX2FIoYOz1PjEevNI5qKY0RXNawxZTNU+uVx02xlAhKqu2t32TQzc8BuloaumxjYld4iKpAkV+oL3j9UU+JYHHrO+VyuRitHHOgRHnvR372E6kVW/RD6+PX68O2cejrA2PRqkxdHP24+tV9s/FO1fC3c2udYGBFWVDnBDkxXr/eybXvLRCx0XLdDXSGpu4VN34Qk4mYfOGgkZph140mDYNa+amoUjIVSEM8QA1uFOVQmm16FIIS1FZSN4JqrMg33iyI8YNdSxR71RLZJwIBNQiZcwFm8ybpNuQQb2n2GQQ/+7RdIDCDYjKtgEaAMAwGos0NWnEVHcZdCup+01xjfO18O2D3984aAQui7Q32N8NwtPJtuKKxG7yo+RVtScPblcWJIcvB5Q1xoy512Oxp5IRDuBTU0BHjqw8cxUW4WjXtc1SfmAzu05auQMWjKX62l3DhQyEcUts5xc8MKmUab8RUJzR2g9u9rT8lCDU4KtROTjXHxEVBDSjyjTc5MTKCb2BvzwqaOi3U6gXVNThTQY0Kdi5gZSvg9JuIn4to5KRNZTJqJ7gBTatWtp/DkMJWeNcQpa2hO4eIV6NeBRQUh9Wh6EiMH6XgG992Ef9tgFND8ZQ4/kUIqfw+J22qG75whBj3chlrCAijvOqUMwHcWHM3bgxEGDCyWIIKwCN6hlGYoZGzbPP3zLBEZoN9gmq9C2BxYNhtRVYc0hF/hj1uLZYew7QGlm3GZN3ZqFTpvCimsm0S14HouquCh926aKfnRvn+sLZ+Cpo6eXD7DnpD4ZuLjFGbD6uVjSfG3P6aiamJ866JUXDFNMsocUQsnjQuKZi9DLYcg1pOiqnA2kZTSWvcfoClzp3wsgEo8r0aWQvf/97BpVFZ56FKHFSPF4BqYNCY2PRGN7bfwrq/pm2rKUPgWzEuRQ+CePq1JKZaWy+sGHcFnFoLlo+hTsWcPdTa6fD8PY2xYn7NvWHw8qNnu6Nn63SWzB/dZCLohtUyiQmqBKZ4TS1QPL1R4UaLh8MSE1e6rPLFc0JtsnfsUHQixuDFW+h0tct98Zs2YhUUNbRE+jZar83Mh0R8dkU+BP5+HuTUJRRC0GRuSde2uLxkfo+2Erpl403w0q9ihKgIvTDo7ZAVdWjM7Tcf/sU/gKNuwcLXn0x/czXe1N8ho7QmAHDscg7cCC7caFc0LEpJ/rPDjkYK6gBPL6Z+qGVpcNk9qn5UeXMGOah6kIt+yNUMLfKBZH0o3/DcAJim0zR2EFMXDka/wZGyQoWXTULRFZ/hq4/QHdaY04dD0/677f6CwfFDWDsS49m9k/1mYUe18yLI1Zetpe1WmVMADJNUmRvBWnHIFE/ATGiWizvIo3UbhyI4Rk6HH/4oghAYG7FUYZ7mtMTtYGD1e+lygrVhayYt0RYpzuJDodUMTWqhuaC4hMY0mX1zklF9MrGsKHY5TsUluOolSRmeZacXvv19FX6A3XcQJA4bH7T+YPp1Z8eUu8n6SeIR2nYjk4gRrlxukxtPummPtnzT8rKtVQWZ1lRidJIStSFrNYFnsqZevoCXtkhx6z6N+9ASnePFLQo7J9rbPr4Gh7a9yIweANuVbJl1Gy+SzO74t7IbvmpoLdiHdM7vB+xIPfluOFQ5aNKvSBD0y49w+cK+xnOpqSorQINj7VFtbcr9DJzN6kufb6G4sN+MuPMJumHQ122pCKzVyJLAo3ggmWualUidrak6ati0XvhiV1B0Jcb8BG/+WWwC4qDO1Kyc5gv2aC/rw2Sa05nVIik2q8SH8uykp0eFMgXlt+JcY+Tab8wORpdgP+amDTyMG5kNak9Q9ftFwSBw2csgcJ/YbmhceBr/TbvjTeNcAtxHvxMtkRO7ZZMNnPmw8u6R+tC5AEvAS/nxNxFc0zxn2SdKi0be7iG2XXVZu7SbZHhebOq2DcKXHKRs3gn2AZAwDD/+6+lnUUHLTtro9STzO8SYt1iHCzRaUeZg2oGqmg00ekOcNEYn6bQiFCuIqdAbVkGja9nvhqeji2PdSmaG6Da300GR0U3bgPO3YSXKrqo5ipDM75k3Lk8mVqtZGLW09pc3cWqdYBAG3Z1DsTIac73Rt6Ml+DR3bLACU6MgY8WwSCZyaVO6jDLY7OXV/Bw67z+jmy2LSm826ofOEbnBPh9wcPFii0apcDjwNzAGvhm7Qn4wML2CNhcC021Qhw+ZwQZCY8RkOPoJMXXGDUzVZjCKusYTaMGqZG2kdGrOrGg48M0alBNcvfLceLwtTadMj38neGnb6ZviScW0w5DyWdwo91tmGxtWSOD7NtRPO+Glq3QqdhFtw0Ljuz+y9UarEqgPmK+5AIJj2Ui+lgiu37seH1YiT9DsHc/0+IfQB9E9zwVrEFYJi69dB7UaGi9ehO//1Vemare6iDpiTOMnlowKl2XPBZj33SF2QTRvYmHjRt2iBNb6VGY2JK9zNbch7LpLpzvUFa1ozG2qORoj5ryQngrP9avnJ6tqyivqR0anE3HnhsACcKjXVoHiBEMzBGMRX5IY5w9cGfX8Wje8cfLsNwlFbRoxB42/+2+CTKeq4y6DSnmDVbtBzVM3fT1u/cB9sLwiY8wJhVvs+tYT5BYYpo3OBWgq8ZDrjZP7UpH60WhNLXfn3OUKttMmWbE6GsMofP8nGN7s0EbWlNOq4UFcsp90RlrOfo/GbbAsmDARI8vHGMFNxeW0sZsO+1z6OsGAmY3R8E5STqgzoDF484fg5n3LNac9QZcZHgJ7zUvQEKNdYwzhyrxCSiRJhz/oFyMjN8pbLdb44i5L+SB6U3mKNVrxrZIJZ9kHXrwP3v5BNuRU2R2qGQ61Rvxraar0iF0iSuwbZjpOGgDeV6RqWzHOx9EYcxlV9A8gfZTvQe1IYw5mc4PNTSugWBeNg5vw419Ed+D2xPYUNAXMZ+4wGjOMG2rNQYfv5XgiJjFGwQ17DVH2V6zNjcJhCyKWxih3kOyo/fCWNNgwaybuKdqGi8aoG374c9uF1f0Kxtx0gJrqTiXAc4gxZOf1ExsP1I8nZ65LtiwqlnTaeVvn8GBvqGMqkzkgb96H7/6UwZISumy3HMDEneBegrX5iuPAKuKqdXtpN4rT3oQyzyjqGeh71ybGADqZjNrkzj2tjBzvhy9D6LYLimY0MulR9C6D9/8GV6/FCbRdi9C7GTxlmlKTZ56M6lLlTRtyzfFwuOviLndZd1KdwcZhA+/9S6RcYTWMgtvvwrc5PVbcIxUagQ2Rqq8tXQPWR8bjOb43H2xCgWSfMWcVWOuHS51x8UqTaZpM2DJqG9T65ls3uIxqb4S+Q12xJhrX9HgRfPj3TGT1UXKtbUn8YJdRoVdaHhhbJOuC4Az2FO2izYiwWqwtBmDrr3UQva8zz/dhtmkEjXm7fhe8/7MYvmxS8CPSlBqrM6Y9H1NLZJY5MmeZlb8bUGogEfuZK4HGuL/SnM2I0ZrgH8LNqa5EGSXyK7uZKiTuXFqojcZVz7ATvPw+ePOnTI30RLTrho7rkNWUmsmoyg5RwKmXoYYLlQyths0PHRV3EJqC4ubQn0YoUeypto3eWuOExpW8+uZfIANkZLNWlTjAMUdWq8JZhZ5abn3dOxUvXtL8GHXHgQhxLwuSSEl+SfxsJsZMRs0e3umtWZ2GKFHsM4u/pllVklczQL7/C9x+L8IzzYjbPSuWi6JuXnqN8dkqoxKkKPOYpSAPsAN02cqEozu2TMXD6F1T9zza5wPOQISIrh/pOsPwNnuJ2QgfPuFsVFejg6pvNfDY9T4za50R/XYdesGKiPUpxYctHoVL6CZkUHyKOtdGKA5jPKkFqxve6DaKctqVdN+s2KDqKHFp9wJe/zF4/UcYvKhtWWng++kx08jqwO9Rq9T60jCjYUIQKmMuzTfGzKkgz+vvlCVxym3ILPFTJfiiqh2VvJSLqMk9toMDzFYjGqsD8vb74N2f4eotOEutzWY5EI/XvHU0V7EkLBo8IY5puSiYUhCrBMgltiTGEC73/Dh2tyz3wpe6AjbVspQicYi2vNZGhFVptLALV+/E1TuYPOD4G04euA+kpVU4wQGHrAE0fyvpCymH0xXEpzhPk2kQ9vTEeCXwrmj9WRVdLddezZ4oMQOW3xnLPA1b3RbV60P9fUHDgqSeCFYH5D8BwmHno+sUbSMUOaqjAavmb5sf1b+G7JUmOL4T428iHrsBr2KhAGgSdpbAmkpXzzsq5ZxJo94mybMJimKQbwKQb78BtOSL9B+Mm4c1b77L8dAPX+uqZlRO3D0kFK0EWAGQ0lFBCBevRPZKZjh9zDE5n+xM0Gl0A8a9ES6Vzpfa4bf9W342kCTjCG8AAt2jCeAywYcSNVG/ceFR7EaSXV2YEtze6bukCDPBGR1+Ttjw5iqvEiSZCa7DV2KYY1LMnnH6IGYjgYk47QYVF4AUay0VGTFG0aWeGIeJeOB/A6zbDZbyb4NovIg+Nk6JrYDi7uiRxKQYdFe21vkoVybnzxRV1lPogP8nsEawysZAD0wqlgY+XN2oDC8g116UO6PxWpN4ZIBiJqjMcTdS5l5aJ7hwStXngzNqz5fk0GNlQJYxmd/UC+hcLObOTEwfcT7K/j1FMnRryE6P0o6AidF4EwbQQ5yiHY3ajXTWP1FrfGFo+uhOltkBF53v66Ou7VBkiqM1AUlgMqPK4SsQi52rl5icPYpkXn7ue4UFVBoGGHRpORoxWM1PFyZUZNSx0XhzmYhpLYnzQJOzH711yg92AmfroMgEW31AEpjMWu8KeldCvF9olU95EM/8JKiSr+nRIqvTwrA03lyTxps8ETWDIn5dU57ix9jYb9DJEgMbngQkLxg5yxjq+TaAbj98syNKbCkU9wzI0n3cUuXgJQwWaVmzjCqfF1Q5q0uEjRc1BiP3maLb6MCgJGPFQgc2CBWNMZlE0VDTtbfY8u3IzGbD6INT2W9XcEYt//4c8ylBbs1SZfcKulfi4l0utWYkmWFy/ixOsS0T8xuRtBd21KFWRoVBik+1HpMg7EpYdxht6wY3BmtNhYjT44OiE/U1RZJ6quyI8KXov8wdIUuejJ9FvvvnXioWg42U9N4Lq9V02RZujGbKlqTpPMVERyMBXNaE4n5nYDjs/G53ounRQLEaIBvEJDFmNsMynuwuSrbEkxyQ86ccnLuoxl0x5LvK6pAsYpIrC6Vld388CjS5GoEYymwma2osO2oTZNcO0fTIoFiB93aKye2wUT9/LakyHheokpGcsdOi1W5BqrBQFEOsBWbpwCSZdjq6HhFAB3He/inXCS57+h1pmqLE44NiNUF0F5ikqbJzmb/yaThZwPJeJNMd3ofGSHERzL3NDnIwPRkoNHcwpvMg6OiIMYF7E3nZ6XBrNbWSoGuU6fLcgVE0bRacRwnFygDbEyZzrbKfv3q3C63yaSHBjhq2GTplMTO2jshtp444tLY4fu52NZtJwUDg/X4kzMpj9sN3Bkdig5R43FCsY63ZNSZlrfJGLKdjxpPzTHx9EmncKA7BHSMUbDB0quOvpdCiaySdkncJ80iCYcvnVTe86UevnXB4CjGoByFJGjy7u55okL/E21yTzAA5f4JkXDygcSnV1ANU6XQXCSNpkkzCsE+ri6KDoqXqIojwItqTaHpqUKzPePujykx96t5mL8wtPSPIJdin2sJgtQGgQIl8JjTzoZzBmM5IKK7URXHfzlk0jL4DvdW0WdH0ZKHYICb3Yum5xqXFP0NjBsvsX0x3gTlzWyuKzbeMFYXGpdHacoy98JWTQ1+cduBbGzC5V0tPdJm9ULzLAJnBEuKCmQfq+SFtR8cYrKVTcKNBYU62Wv1MkmlIRYcHueWGvqQDZkuF0B+E7/Z/3rOAYrOYFDsNHsifyUX2yv9YYjIZ5arcLhslnTZKjCkNxQUrBtpvdyA4DqPfuYqmjcir5wXFprhuHwE9RUwmC8E1w+RadnXM3DJ1y3eDyvEA7kxI/EEem6Ra20wA/RRbFNA7CD+47gl1doFvu8Zke6iSin29wDDPcs4ZMlcpm8zbmpt8GG641mHY4OsHkc37ylB0SB3m9OoE1wbvxY6sNR6Kx0SVmwFzQGav7pucIeNHSMdVYCPtGCVJp1w+RFDHMR8YJ5MORKoHOIBhgl/b8PQD6A6N3ovdiaYeijunysZ5cm13DTC6EtEV4hySZ4jvRdVgzsSJEhkhO3p1cdahbgiIrkpt6K4yYm01cxj900FURA/Fo7T0KJjsYHSTvSCdiPgB0meXMnawkE4jefoCSy102zlkyYRp9lLz+kGEC0f/7LBPeRB9qL9tsIfiOVJlaTQM+qLbz+d6OlqQJGtmJyJIuXWO61fZyO2oUTggJcMEnaGIzfTNP+qGt73QWUVsXG/0UDwYVTaIya3gGl5lL5FOIXmA5NFmsIkYWKqTxyVXOk5jEZKd+kIcLI04hMEg/HBwHHooHpIqGxRfy/AOehi8wegVJPeQPgG1CWlGiYsIm2peew5Ky06OOJ12xRXFisNtNIByJuTSHMr/MO9baA5w8wKqp8qGSDK6RXELyROkj7k+WaZEt6XdJasYdMYbdfclEF0dDmuLpjZTTfi70Kgi7o0SPRRPE5OKC+Qye4l0HCQL084i4jQpRtjoi+K4ll00U2iSzikohvoCcBY1rw4u++GbTnjdEhx6KB6B+FqnGrp0bDBIMwbAOEi+xclhLJYp0omageilhKMfLbxYFYfZXVl489+1B4fiIFudni0sXTdyLR5Yfy1YvxWN4fWX+O0U+yuuWu7vsqr1BsVy4LJEarDrQKkDripHFV6Lf9I0Idkvjwun8MbEmlWexbKppp+piG2bIR6Kx4HJOkguvfM4wyQNn5Kru/jlNO3v+evHVAWDhWuxuADQOETNm/wOSyRaTTX7p0QvoB6fSlmtos+m8zTB6RoLicgBOU6Hg+C5F0zZmqGjZVWOlctdGkFPucJOCUb274YVTTkX0T+1x1TjWfF0eNIVwPcT3BLPYgInuGTI21na3cOXTagYvbzODQuHuNqWGyn7DdpJdBB+FwUXLcShZ8Wj50mnElvPc5ylSNLaApDXEc4HwTgCQ0Qrz7gKxT2Epc+1EXC7L4vaCW674W1rn75nxVPgSU7PDBr3q00NcWFEzF9F5sh+ztPOQ3w9Si5Tur6Gy2bp+ksiiTFTF7dXxdYS0dihuF6EcGE11RyQEj0UTweT1j6PU4xT5GhV07T3kLyYpMM1sEC1oGLpIyiVkIPiTxmmkFCWm2CXdW5CGAyj79uMQy+gnpTsath1KwPh4yxdQggKO5mufsUy56UI43SQYXIYPnUgZptHgGNqSan0kYWAaqC1skPfGOcmGWAhN5l+H0DUZhx6VjxWnnQ96ts4TVFrCCkZctaYz4NyMgXyKbnE8l6LGiakvY9lXs2gqLLzjlhxUc7094Fxq+A9Q85D8TRJkvP+JMZxjGUVC8tBn0gSH2YKZPc+fpExJCmprt5BYajdhkrAgEqMC12RVAUJqykW3kQjVw+i78JKiYj7x6eH4qmhUW1fnxNV9lMAKaMRZSAhPCcXT8lV6jZhQL/7YqwKqMxBke1FHITfdYLrlquIHorn0u4naZyuBE77JDcyTIzRY3w9x44ULkeYdQBXL3U0WO8eR8fc0CqiTfkkWzd4ZXVdtAeHHoon3jIQ3k2SLQViGZBY8GwUVEfCz7H8IEUYJZej+BKx1nxNUtpyQ5tqtn9Lrg7Uq76d4HYQfTgiHHoonnj78hzzKIT4XKuDZdojdjJ6zEhy67jfMiGgzWyzBFWq7EewtNzoTKZ8Qozgoh9+bJsq6KF4vu1hmk7miAq9rRvBkIotZ92tPERGj8FTcjlJqkeTI2G5CQ2uC7QsEis+DGAwiL43I83J6OWh6Fu1+Y1b0XSc7Pp0k7Q/ii+U0BwwBAZs8qcSFYorAZWVE0x+vnDl/7PZhdhanvQu/qNHHdk+j+IEl0aVwtyWKgkvtUcQhRi1jcIomWDWb5RjAxZ/ZsJqguEweg7BDfkEFPPZqPIh6dcnm92V30IV0bPiCeJw82Yumsbp3i4mY8WMG+erxA7QkBhgKTVZMaKCqFPuye7KbzMOPRRPsGUY/PYcr93fskpIesuLCqEq/hX8j6vRkNDiMpg9J4NxMqB1OBLAyoISQF9rLzV583MNMcOh1ZXfZhx6AfUEKTETTVPKoV/ObgJjH3nS08nCxT0zFr1naTfDZD+cBKs3NLMcVv+nmITQwAwc2rKBRStNpp4VTxmH95N0PE/LnIYrTpPYTqXLlXFVYyRRD1VIKnt3nnSe42FqqoWzhWiq7KwYwBA3fLj5zcSH2LdlA4u2mkw9FE+2zRL8OoppsVDjGKfzAJFV0IIOIl9kIa/RKKNv9YKCkllXoc1wWC2kpoU86aF4IpSYUeGvT7HxYCoym65DQWzMhFyILlI60nA0v0gkJ8fWZrO5BhWKkE9Ia5w3bvjwZHDooXg6oum353i6TA1WAtYEZYMxcmPBlkPCVLLl0NyYR5DHwwRNE0z1Z+SWGzYfdoIXJ4NDD8UTweFoli5iTWm4seLAUSusImO7Q1TIdEF6SzSG2dyXCXnFkKghVRsf5iGmGQ6rIaq19hsPxaNvcZqJpvOy4V/+VRVKN34O1dVhVCRVeVVGmXxEHj4eD5I00BNjLAuo9lSpDIf98KMVUS13XXgoniAl/vwwS1I2AXIYsmpfpBRNXKAxTcOCxigbfyQBtWMgbjxdHHooHj0Ov4wyFZHwg0ukJ3OjTGZKuLco+Q9MEeQk4W4JczNEjsb+Go0yK4qUgffVeN089em7k8Shh+Lxq4jPyZF8kRyNqhWHX/F1icPjVQU9FE+2ZWT46XGOatiaZINRUoEp4+qmBz9KDpWsYqQsqxInIzzPy2gseflDuBAyfxf5sDIOjwKfHopHSYkpigyHSYrH9o1gMu8VEz8QTfvNYBN8eCw86aF4lKLpr0/zQu7FmpaKpKZEjaFKj0i6+1HVMzVRcubyHEiW58hY8XnWx0IhRqFRP5djd84Dhx6KR4nDu3HyMEmO99tlaJzErK1yMj4cngcOPRSPr43n6a+P8y1ZKblNhJ1Tdhiq+1JgqQgj6TAkdrTYGlephYOqELV+c55EGzTGMjEGK3XxjPjQQ/H4KDFO8ce7GbmjWVnRovzzxK6ghl0NjTEvKvA4ptAi4GdJJwOkrmcIg0H4/nxwKHy+4nGZan74NksK2IMiGkGulLH6tTAn1wcCKIDCbaWNctZiYVeNUnkOUPazQQQQcsUOsanGUb42QBjPe0GQ7/Em5D2AQ+hfRL83bwx8Yjj0rHhMKuKnx/nUUCZDH0SqpSbujtk2iuMcq+HY51k/kd+PguFF9Idzw6GH4tG0+0lyv9z4QhvvqfME6uLTJBRzIsiRqpTKUVZ1oy0TOIpiagakfvjuDHHooXgclJh78x/mZSShYnHRDUxyY9nqg6gMr6Y1opq0qElStpUhl4w7TkA6SRx6KB6HivjT/SzF6rtH6CRV0ximLXxR1Iw7p6B75jj0ZpsjUBF/eZhN5qlYm0mKIiSgUth0nQW4nuDLf9afFGs/gWTyWb6Ba6sNyB9tjyju/FYYCFE93Qb8ki1ne63u2DlhHHpWbHt7nGYqos2bz2Uo5NNURdLjOT/UjRzr7HB+Gjj0rNhqSpwn+NPCiwiKhLlmSJTICgq/CoL0Sn4OUXRIrMcqdYPSwQUyI/wcy3Ekqhb2MuQM3J88Dj0rtlo0zVTEBP1NOgscelZsb/ttFD9NE7HeU1RinjUjydrjWhdcMk1Rx2PvmSH5K0BRSoWkPcrEuz01yA6UYkRB+XSLdzuhx6FnxRaLpnmg6Xm0QA+q88GhZ8WWiqY/3k0TVF0HWyosaY9m42qBKwvdCtbOzRbCkqlTsqCWSY8kXtWyKijjKhauAJqzo3pW9K1hcH4dxaNZej5fvB85UN+p4tCzYosocdly0fRhVlQFhcRSK4UQ5SlJ9S+GjUt2SoXnZNotD70lQFgz9PYvIL9dkSiBOl1ZezxzPvSs2EbR9Ge21dS814UhGs4eDm4NpdvYd+yns+Rd9aNzRJ2HYtvbw0RKz0fU5wEqBTRIfBZKZJjKehMh3RRk6SBvHf55pwvYkDt5cHoBtS2UuIg1ncrehK1wB9QMhxUorcLqeozSDt7KhopyxiMVr4ZFj8jyL81wgvJzKMIq6ck4Q0r0rNgicH5+ms/i0uYzUjkKvaxJSJREfURz4BtqhV6SeQ0iLvKqASypOgpqRYF7VvSteWvNl6cZhZFy+nw5omxlHyEMLuRoIJRAOGOUnCbmTbXlFPwc5MUSoeh5p24AfmJ4VmyRtSZ3JKZGm4wwqXtaXRHtxh6yR1M5UNZxuoonQ11PzkRe9ax4eHCOFukXK699gXm2vxZCrjl+DqEGbUuh2ChkX0NZ/6QcGJu3wMSNaxUXldSt4sWsv47Oo+hZ0bcDiKZikZHoPmAtsqr3XXjnYXTrhh6KnhVbA87fnuPHaSEjscA8hTjvLTeuuQtkY2YRA1utTIogF8XIOTmbWOij5Mjk5CIzU8F0q88LUQVEQAFgx0PRs2KLKPF+erZ3ZtCpnjHsWdG3RilxsTtiKXxabJKJSpQi2Se3Md+oGCaFVDSqXNGCSrCiI8iFqTxH+SJV7XGrpVIR5B0A1ZNxnh5Fz4qHp8SfH86XEvsdL516VmwNJc7icgqUUJx1AkvaXVEBW5kiFQokdDs6glxITCuHjWvLc6hlyEFxdxb9oVveLhBvP/I04Fnx6CiRjPM0lg+2DUO/g7wDzcE9ghpQPeSi61nRs2ILwPl1NJ/OUU0ykotfIBTZTo5lAUGbQdHIjUJTngMILZEwrhZpExnlOWR63Kq43QgC8FD0rNgCSvzpfkYWOSyRHsoSpZnBikfYQ0M19GXl1Ur3onz2yy4x8c42zsZD8ZCUOMs3oqFL6AstGjXCKrmLBmqj56jEKELItMfZsffMUIE58DYbD8U2UOKP9zNUpnNpY1GK3VDI+3LrJjoWvf064l1lPFrCVkvbd2suitj5Bin9cvleFEDP22w8FA8OzsdpsqBESyq9kGFpkDbRKlI2VUy1oWi7i66fdd5s0wZKvJtuzCRrw8i2DhqA7LVf/q4EXm92qiiOo40gF1Y/x1o1U4iRKkMOJQOPoK+wsN2qkGIArnpeUfSseGhwzhJ8XJTMQJtBBSvthYHVaIwXwW33nqDBnrRqvchLp54VW0CJP9xNUJTDwCSywkLyEpDbn21pCDaFRxVvBJQ8GJsIciEFkRdVPlhxqak8ByuIvBT8Lfs5rnqhnySeFQ8MziQV357jM78bXjr1rHj49vlpFifFeDOJHose81UsNUI5n1jRzaSNnwrx2avRlAhyUQgiJ7RHLIRxK2XIy/tA6cpzlATZwkVedMPQV9DwrHgQ6bT4zs/uKcLnQIm+eVbcaxvN0nGcgiiTFcgZT7IWKfFLUWajIsiFUCo+SYFz28FN5TlKgW/FKDnJsrpVZe3lOZbn6wRwSSmK4CPgPCvu02BzzvlQy/ZiwDXYnCc4PSvuA5xJil9G83Sh3IEcV13KOdrSImwMpBuFrUxhUODBwvaJUmEoKs67yI3LkSlbLtFZomVATaUNNYI8rwIurvvedupZ8dDty3M8T1dQQlLILLxpcfJpYtk2P5h5T+RgTu5N5FW7Wo5w3aMNNl469VDcq8Emo8RVfOk67FP3Wk/f4j6E2g1nUIPG0ttIRpCSG1wYy5Ajrww56em/HXLlr7MFp4fizluc4ufnFRQX6RiYLn8pxXzLcdVYyHLA8kTHZRNKBDkVKU5tR0MCjUKXgl57TKu0z9Ti500/9MXdPBTbIJ3ORYn6CikRGlbcorHEdjKZaTtgOZtq/Q5qubEEQD0gCTQiqty7HffVRcQkwHOWV73ZZg/SaSw0Fo615aRsLYF1fW3Vz1E0h6wiysjpW4g0lw0o1F4YcoS5beMNws9RRHypDPnNwFOiZ8V2SKeZoijKdFd4IWszDIOJBVGbua+PYTWcD5u9A68vOl4n9KzYAuk0xyGRQYTbGDepHE2gEFcZHnL58KKfA9bbVdBoXEepITWO2NAjyNu/qcVOhZSTRfg5Cj6X20HEp8QzB6dnxR1Lp89zYWQmkvo2ihzqeIpKWOJkCTN3khJGQrZosGtABgCvLyOPOs+K7WFFRXvb8k3e0gKbQJEbFzS1VQuB1gKLKmShMqliESlXaqPLkENZmbRtYoWF0eQy5LdDh+BvD07PirvEYYkSz6llcumby46fA54VWyGd3o1jk/yHUsnsJTcGi8PTJROumUINIhfKnhkbjpW0x0J5DiXwrcBmmzLk8gaP29GUCHJhK8/x9qrLJ0BPiZ4V9yadnle76IY+4tSzYlvaJE4nccqwkEDRwppKNLhSvgLqIIJjNRHkIDsmVW6UsqnW+cqq43GzwaO5DHkI8OGm4ynRs2KbpVMDILd/lLyOZPJVybKKawsLGsoQK28Wo234EeSidIRiPH05jLph4G0znhXb0u4m/DI25Q1+ixiDFT2uZnGwpqmSirglt0KZC1GyrIK85ZOQTiNVglS26BDlTaxkPXPdtx8Fb68crDUenJ4Vdw/FcaWKUkqdbVzzLRmtqnamq4yvO5GpIeo4vDLkxL4A373oetR5VjwuRZELTLBVFhVk2KjKaVadFYlOkua5+V0z+JvLTr/jsLh7cHootkdR1OCL3ou76MAvSosCCxuOlsLWiln8qp+jDHssRZAL069yLPlF1ySaetRZ2/8XYADH/GSQm72TNQAAAABJRU5ErkJggg==" alt="Yii Framework"/> <p class="timestamp"><?php echo date('Y-m-d, H:i:s'); ?></p> - <p><?php echo $this->createServerInformationLink(); ?></p> - <p><a href="http://yiiframework.com/">Yii Framework</a>/<?php echo $this->createFrameworkVersionLink(); ?></p> + <p><?php echo $handler->createServerInformationLink(); ?></p> + <p><a href="http://yiiframework.com/">Yii Framework</a>/<?php echo $handler->createFrameworkVersionLink(); ?></p> </div> <script type="text/javascript"> @@ -484,6 +477,8 @@ window.onload = function() { } }; </script> + <?php if (method_exists($this, 'endBody')) $this->endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?> </body> </html> +<?php if (method_exists($this, 'endPage')) $this->endPage(); ?> diff --git a/framework/yii/views/errorHandler/previousException.php b/framework/yii/views/errorHandler/previousException.php index d56f6dd..e6dcf87 100644 --- a/framework/yii/views/errorHandler/previousException.php +++ b/framework/yii/views/errorHandler/previousException.php @@ -1,7 +1,7 @@ <?php /** * @var \yii\base\Exception $exception - * @var \yii\base\ErrorHandler $this + * @var \yii\base\ErrorHandler $handler */ ?> <div class="previous"> @@ -9,13 +9,13 @@ <h2> <span>Caused by:</span> <?php if ($exception instanceof \yii\base\Exception): ?> - <span><?php echo $this->htmlEncode($exception->getName()); ?></span> – - <?php echo $this->addTypeLinks(get_class($exception)); ?> + <span><?php echo $handler->htmlEncode($exception->getName()); ?></span> – + <?php echo $handler->addTypeLinks(get_class($exception)); ?> <?php else: ?> - <span><?php echo $this->htmlEncode(get_class($exception)); ?></span> + <span><?php echo $handler->htmlEncode(get_class($exception)); ?></span> <?php endif; ?> </h2> - <h3><?php echo $this->htmlEncode($exception->getMessage()); ?></h3> + <h3><?php echo $handler->htmlEncode($exception->getMessage()); ?></h3> <p>in <span class="file"><?php echo $exception->getFile(); ?></span> at line <span class="line"><?php echo $exception->getLine(); ?></span></p> - <?php echo $this->renderPreviousExceptions($exception); ?> + <?php echo $handler->renderPreviousExceptions($exception); ?> </div> diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index d864d95..c2f5c4b 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -864,7 +864,7 @@ class Request extends \yii\base\Request private $_cookieValidationKey; /** - * @return string the secret key used for cookie validation. If it was set previously, + * @return string the secret key used for cookie validation. If it was not set previously, * a random key will be generated and used. */ public function getCookieValidationKey() diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 5371122..6bb888c 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -258,7 +258,6 @@ class Response extends \yii\base\Response $this->sendHeaders(); $this->sendContent(); $this->trigger(self::EVENT_AFTER_SEND, new ResponseEvent($this)); - $this->clear(); } /** diff --git a/framework/yii/widgets/DetailView.php b/framework/yii/widgets/DetailView.php index 229cdec..c3bf864 100644 --- a/framework/yii/widgets/DetailView.php +++ b/framework/yii/widgets/DetailView.php @@ -21,7 +21,7 @@ use yii\helpers\Inflector; * DetailView displays the detail of a single data [[model]]. * * DetailView is best used for displaying a model in a regular format (e.g. each model attribute - * is displayed as a row in a table.) The model can be either an instance of [[Model]] or + * is displayed as a row in a table.) The model can be either an instance of [[Model]] * or an associative array. * * DetailView uses the [[attributes]] property to determines which model attributes @@ -105,7 +105,7 @@ class DetailView extends Widget public function init() { if ($this->model === null) { - throw new InvalidConfigException('Please specify the "data" property.'); + throw new InvalidConfigException('Please specify the "model" property.'); } if ($this->formatter == null) { $this->formatter = Yii::$app->getFormatter(); @@ -166,7 +166,7 @@ class DetailView extends Widget } elseif (is_array($this->model)) { $this->attributes = array_keys($this->model); } else { - throw new InvalidConfigException('The "data" property must be either an array or an object.'); + throw new InvalidConfigException('The "model" property must be either an array or an object.'); } sort($this->attributes); } diff --git a/framework/yii/widgets/GridView.php b/framework/yii/widgets/GridView.php new file mode 100644 index 0000000..cdfa782 --- /dev/null +++ b/framework/yii/widgets/GridView.php @@ -0,0 +1,328 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\widgets; + +use Yii; +use Closure; +use yii\base\Formatter; +use yii\base\InvalidConfigException; +use yii\base\Widget; +use yii\db\ActiveRecord; +use yii\helpers\Html; +use yii\widgets\grid\DataColumn; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class GridView extends ListViewBase +{ + const FILTER_POS_HEADER = 'header'; + const FILTER_POS_FOOTER = 'footer'; + const FILTER_POS_BODY = 'body'; + + public $dataColumnClass = 'yii\widgets\grid\DataColumn'; + public $caption; + public $captionOptions = array(); + public $tableOptions = array('class' => 'table table-striped table-bordered'); + public $headerRowOptions = array(); + public $footerRowOptions = array(); + public $beforeRow; + public $afterRow; + public $showHeader = true; + public $showFooter = false; + /** + * @var array|Closure + */ + public $rowOptions = array(); + /** + * @var array|Formatter the formatter used to format model attribute values into displayable texts. + * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]] + * instance. If this property is not set, the "formatter" application component will be used. + */ + public $formatter; + /** + * @var array grid column configuration. Each array element represents the configuration + * for one particular grid column which can be either a string or an array. + * + * When a column is specified as a string, it should be in the format of "name:type:header", + * where "type" and "header" are optional. A {@link CDataColumn} instance will be created in this case, + * whose {@link CDataColumn::name}, {@link CDataColumn::type} and {@link CDataColumn::header} + * properties will be initialized accordingly. + * + * When a column is specified as an array, it will be used to create a grid column instance, where + * the 'class' element specifies the column class name (defaults to {@link CDataColumn} if absent). + * Currently, these official column classes are provided: {@link CDataColumn}, + * {@link CLinkColumn}, {@link CButtonColumn} and {@link CCheckBoxColumn}. + */ + public $columns = array(); + /** + * @var string the layout that determines how different sections of the list view should be organized. + * The following tokens will be replaced with the corresponding section contents: + * + * - `{summary}`: the summary section. See [[renderSummary()]]. + * - `{items}`: the list items. See [[renderItems()]]. + * - `{sorter}`: the sorter. See [[renderSorter()]]. + * - `{pager}`: the pager. See [[renderPager()]]. + */ + public $layout = "{summary}\n{pager}{items}\n{pager}"; + public $emptyCell = ' '; + /** + * @var \yii\base\Model the model instance that keeps the user-entered filter data. When this property is set, + * the grid view will enable column-based filtering. Each data column by default will display a text field + * at the top that users can fill in to filter the data. + * Note that in order to show an input field for filtering, a column must have its {@link CDataColumn::name} + * property set or have {@link CDataColumn::filter} as the HTML code for the input field. + * When this property is not set (null) the filtering is disabled. + */ + public $filterModel; + /** + * @var string whether the filters should be displayed in the grid view. Valid values include: + * <ul> + * <li>header: the filters will be displayed on top of each column's header cell.</li> + * <li>body: the filters will be displayed right below each column's header cell.</li> + * <li>footer: the filters will be displayed below each column's footer cell.</li> + * </ul> + */ + public $filterPosition = 'body'; + public $filterOptions = array('class' => 'filters'); + + /** + * Initializes the grid view. + * This method will initialize required property values and instantiate {@link columns} objects. + */ + public function init() + { + parent::init(); + if ($this->formatter == null) { + $this->formatter = Yii::$app->getFormatter(); + } elseif (is_array($this->formatter)) { + $this->formatter = Yii::createObject($this->formatter); + } + if (!$this->formatter instanceof Formatter) { + throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.'); + } + + $this->initColumns(); + } + + /** + * Renders the data models for the grid view. + */ + public function renderItems() + { + $content = array_filter(array( + $this->renderCaption(), + $this->renderColumnGroup(), + $this->showHeader ? $this->renderTableHeader() : false, + $this->showFooter ? $this->renderTableFooter() : false, + $this->renderTableBody(), + )); + return Html::tag('table', implode("\n", $content), $this->tableOptions); + } + + public function renderCaption() + { + if (!empty($this->caption)) { + return Html::tag('caption', $this->caption, $this->captionOptions); + } else { + return false; + } + } + + public function renderColumnGroup() + { + $requireColumnGroup = false; + foreach ($this->columns as $column) { + /** @var \yii\widgets\grid\Column $column */ + if (!empty($column->options)) { + $requireColumnGroup = true; + break; + } + } + if ($requireColumnGroup) { + $cols = array(); + foreach ($this->columns as $column) { + $cols[] = Html::tag('col', '', $column->options); + } + return Html::tag('colgroup', implode("\n", $cols)); + } else { + return false; + } + } + + /** + * Renders the table header. + * @return string the rendering result + */ + public function renderTableHeader() + { + $cells = array(); + foreach ($this->columns as $column) { + /** @var \yii\widgets\grid\Column $column */ + $cells[] = $column->renderHeaderCell(); + } + $content = implode('', $cells); + if ($this->filterPosition == self::FILTER_POS_HEADER) { + $content = $this->renderFilters() . $content; + } elseif ($this->filterPosition == self::FILTER_POS_BODY) { + $content .= $this->renderFilters(); + } + return "<thead>\n" . Html::tag('tr', $content, $this->headerRowOptions) . "\n</thead>"; + } + + /** + * Renders the table footer. + * @return string the rendering result + */ + public function renderTableFooter() + { + $cells = array(); + foreach ($this->columns as $column) { + /** @var \yii\widgets\grid\Column $column */ + $cells[] = $column->renderFooterCell(); + } + $content = implode('', $cells); + if ($this->filterPosition == self::FILTER_POS_FOOTER) { + $content .= $this->renderFilters(); + } + return "<tfoot>\n" . Html::tag('tr', $content, $this->footerRowOptions) . "\n</tfoot>"; + } + + /** + * Renders the filter. + */ + public function renderFilters() + { + if ($this->filterModel !== null) { + $cells = array(); + foreach ($this->columns as $column) { + /** @var \yii\widgets\grid\Column $column */ + $cells[] = $column->renderFilterCell(); + } + return Html::tag('tr', implode('', $cells), $this->filterOptions); + } else { + return ''; + } + } + + /** + * Renders the table body. + * @return string the rendering result + */ + public function renderTableBody() + { + $models = array_values($this->dataProvider->getModels()); + $keys = $this->dataProvider->getKeys(); + $rows = array(); + foreach ($models as $index => $model) { + $key = $keys[$index]; + if ($this->beforeRow !== null) { + $row = call_user_func($this->beforeRow, $model, $key, $index); + if (!empty($row)) { + $rows[] = $row; + } + } + + $rows[] = $this->renderTableRow($model, $key, $index); + + if ($this->afterRow !== null) { + $row = call_user_func($this->afterRow, $model, $key, $index); + if (!empty($row)) { + $rows[] = $row; + } + } + } + return "<tbody>\n" . implode("\n", $rows) . "\n</tbody>"; + } + + /** + * Renders a table row with the given data model and key. + * @param mixed $model the data model to be rendered + * @param mixed $key the key associated with the data model + * @param integer $index the zero-based index of the data model among the model array returned by [[dataProvider]]. + * @return string the rendering result + */ + public function renderTableRow($model, $key, $index) + { + $cells = array(); + /** @var \yii\widgets\grid\Column $column */ + foreach ($this->columns as $column) { + $cells[] = $column->renderDataCell($model, $index); + } + if ($this->rowOptions instanceof Closure) { + $options = call_user_func($this->rowOptions, $model, $key, $index); + } else { + $options = $this->rowOptions; + } + $options['data-key'] = $key; + return Html::tag('tr', implode('', $cells), $options); + } + + /** + * Creates column objects and initializes them. + */ + protected function initColumns() + { + if (empty($this->columns)) { + $this->guessColumns(); + } + $id = $this->getId(); + foreach ($this->columns as $i => $column) { + if (is_string($column)) { + $column = $this->createDataColumn($column); + } else { + $column = Yii::createObject(array_merge(array( + 'class' => $this->dataColumnClass, + 'grid' => $this, + ), $column)); + } + if (!$column->visible) { + unset($this->columns[$i]); + continue; + } + if ($column->id === null) { + $column->id = $id . '_c' . $i; + } + $this->columns[$i] = $column; + } + } + + /** + * Creates a {@link CDataColumn} based on a shortcut column specification string. + * @param string $text the column specification string + * @return DataColumn the column instance + * @throws InvalidConfigException if the column specification is invalid + */ + protected function createDataColumn($text) + { + if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $text, $matches)) { + throw new InvalidConfigException('The column must be specified in the format of "Attribute", "Attribute:Format" or "Attribute:Format:Header'); + } + return Yii::createObject(array( + 'class' => $this->dataColumnClass, + 'grid' => $this, + 'attribute' => $matches[1], + 'format' => isset($matches[3]) ? $matches[3] : 'text', + 'header' => isset($matches[5]) ? $matches[5] : null, + )); + } + + protected function guessColumns() + { + $models = $this->dataProvider->getModels(); + $model = reset($models); + if (is_array($model) || is_object($model)) { + foreach ($model as $name => $value) { + $this->columns[] = $name; + } + } else { + throw new InvalidConfigException('Unable to generate columns from data.'); + } + } +} diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php index dbcf6f5..c191389 100644 --- a/framework/yii/widgets/ListView.php +++ b/framework/yii/widgets/ListView.php @@ -19,7 +19,7 @@ use yii\helpers\Html; class ListView extends ListViewBase { /** - * @var array the HTML attributes for the container of the rendering result of each data item. + * @var array the HTML attributes for the container of the rendering result of each data model. * The "tag" element specifies the tag name of the container element and defaults to "div". * If "tag" is false, it means no container element will be rendered. */ @@ -29,7 +29,7 @@ class ListView extends ListViewBase * for rendering each data item. If it specifies a view name, the following variables will * be available in the view: * - * - `$item`: mixed, the data item + * - `$model`: mixed, the data model * - `$key`: mixed, the key value associated with the data item * - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]]. * - `$widget`: ListView, this widget instance @@ -39,7 +39,7 @@ class ListView extends ListViewBase * If this property is specified as a callback, it should have the following signature: * * ~~~ - * function ($item, $key, $index, $widget) + * function ($model, $key, $index, $widget) * ~~~ */ public $itemView; @@ -50,40 +50,40 @@ class ListView extends ListViewBase /** - * Renders all data items. + * Renders all data models. * @return string the rendering result */ public function renderItems() { - $items = $this->dataProvider->getItems(); + $models = $this->dataProvider->getModels(); $keys = $this->dataProvider->getKeys(); $rows = array(); - foreach (array_values($items) as $index => $item) { - $rows[] = $this->renderItem($item, $keys[$index], $index); + foreach (array_values($models) as $index => $model) { + $rows[] = $this->renderItem($model, $keys[$index], $index); } return implode($this->separator, $rows); } /** - * Renders a single data item. - * @param mixed $item the data item to be rendered - * @param mixed $key the key value associated with the data item - * @param integer $index the zero-based index of the data item in the item array returned by [[dataProvider]]. + * Renders a single data model. + * @param mixed $model the data model to be rendered + * @param mixed $key the key value associated with the data model + * @param integer $index the zero-based index of the data model in the model array returned by [[dataProvider]]. * @return string the rendering result */ - public function renderItem($item, $key, $index) + public function renderItem($model, $key, $index) { if ($this->itemView === null) { $content = $key; } elseif (is_string($this->itemView)) { $content = $this->getView()->render($this->itemView, array( - 'item' => $item, + 'model' => $model, 'key' => $key, 'index' => $index, 'widget' => $this, )); } else { - $content = call_user_func($this->itemView, $item, $key, $index, $this); + $content = call_user_func($this->itemView, $model, $key, $index, $this); } $options = $this->itemOptions; $tag = ArrayHelper::remove($options, 'tag', 'div'); diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/ListViewBase.php index a29fcf0..fe318c4 100644 --- a/framework/yii/widgets/ListViewBase.php +++ b/framework/yii/widgets/ListViewBase.php @@ -70,7 +70,7 @@ abstract class ListViewBase extends Widget /** - * Renders the data items. + * Renders the data models. * @return string the rendering result. */ abstract public function renderItems(); @@ -91,8 +91,9 @@ abstract class ListViewBase extends Widget public function run() { if ($this->dataProvider->getCount() > 0 || $this->empty === false) { - $content = preg_replace_callback("/{\\w+}/", function ($matches) { - $content = $this->renderSection($matches[0]); + $widget = $this; + $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) { + $content = $widget->renderSection($matches[0]); return $content === false ? $matches[0] : $content; }, $this->layout); } else { @@ -108,7 +109,7 @@ abstract class ListViewBase extends Widget * @param string $name the section name, e.g., `{summary}`, `{items}`. * @return string|boolean the rendering result of the section, or false if the named section is not supported. */ - protected function renderSection($name) + public function renderSection($name) { switch ($name) { case '{summary}': diff --git a/framework/yii/widgets/grid/CheckboxColumn.php b/framework/yii/widgets/grid/CheckboxColumn.php new file mode 100644 index 0000000..e6bb327 --- /dev/null +++ b/framework/yii/widgets/grid/CheckboxColumn.php @@ -0,0 +1,191 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\widgets\grid; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class CheckboxColumn extends Column +{ + public $checked; + /** + * @var string a PHP expression that will be evaluated for every data cell and whose result will + * determine if checkbox for each data cell is disabled. In this expression, you can use the following variables: + * <ul> + * <li><code>$row</code> the row number (zero-based)</li> + * <li><code>$data</code> the data model for the row</li> + * <li><code>$this</code> the column object</li> + * </ul> + * The PHP expression will be evaluated using {@link evaluateExpression}. + * + * A PHP expression can be any PHP code that has a value. To learn more about what an expression is, + * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}. + * + * Note that expression result will overwrite value set with <code>checkBoxHtmlOptions['disabled']</code>. + * @since 1.1.13 + */ + public $disabled; + /** + * @var array the HTML options for the data cell tags. + */ + public $htmlOptions = array('class' => 'checkbox-column'); + /** + * @var array the HTML options for the header cell tag. + */ + public $headerHtmlOptions = array('class' => 'checkbox-column'); + /** + * @var array the HTML options for the footer cell tag. + */ + public $footerHtmlOptions = array('class' => 'checkbox-column'); + /** + * @var array the HTML options for the checkboxes. + */ + public $checkBoxHtmlOptions = array(); + /** + * @var integer the number of rows that can be checked. + * Possible values: + * <ul> + * <li>0 - the state of the checkbox cannot be changed (read-only mode)</li> + * <li>1 - only one row can be checked. Checking a checkbox has nothing to do with selecting the row</li> + * <li>2 or more - multiple checkboxes can be checked. Checking a checkbox has nothing to do with selecting the row</li> + * <li>null - {@link CGridView::selectableRows} is used to control how many checkboxes can be checked. + * Checking a checkbox will also select the row.</li> + * </ul> + * You may also call the JavaScript function <code>$(gridID).yiiGridView('getChecked', columnID)</code> + * to retrieve the key values of the checked rows. + * @since 1.1.6 + */ + public $selectableRows = null; + /** + * @var string the template to be used to control the layout of the header cell. + * The token "{item}" is recognized and it will be replaced with a "check all" checkbox. + * By default if in multiple checking mode, the header cell will display an additional checkbox, + * clicking on which will check or uncheck all of the checkboxes in the data cells. + * See {@link selectableRows} for more details. + * @since 1.1.11 + */ + public $headerTemplate = '{item}'; + + /** + * Initializes the column. + * This method registers necessary client script for the checkbox column. + */ + public function init() + { + if (isset($this->checkBoxHtmlOptions['name'])) { + $name = $this->checkBoxHtmlOptions['name']; + } else { + $name = $this->id; + if (substr($name, -2) !== '[]') { + $name .= '[]'; + } + $this->checkBoxHtmlOptions['name'] = $name; + } + $name = strtr($name, array('[' => "\\[", ']' => "\\]")); + + if ($this->selectableRows === null) { + if (isset($this->checkBoxHtmlOptions['class'])) { + $this->checkBoxHtmlOptions['class'] .= ' select-on-check'; + } else { + $this->checkBoxHtmlOptions['class'] = 'select-on-check'; + } + return; + } + + $cball = $cbcode = ''; + if ($this->selectableRows == 0) { + //.. read only + $cbcode = "return false;"; + } elseif ($this->selectableRows == 1) { + //.. only one can be checked, uncheck all other + $cbcode = "jQuery(\"input:not(#\"+this.id+\")[name='$name']\").prop('checked',false);"; + } elseif (strpos($this->headerTemplate, '{item}') !== false) { + //.. process check/uncheck all + $cball = <<<CBALL +jQuery(document).on('click','#{$this->id}_all',function() { + var checked=this.checked; + jQuery("input[name='$name']:enabled").each(function() {this.checked=checked;}); +}); + +CBALL; + $cbcode = "jQuery('#{$this->id}_all').prop('checked', jQuery(\"input[name='$name']\").length==jQuery(\"input[name='$name']:checked\").length);"; + } + + if ($cbcode !== '') { + $js = $cball; + $js .= <<<EOD +jQuery(document).on('click', "input[name='$name']", function() { + $cbcode +}); +EOD; + Yii::app()->getClientScript()->registerScript(__CLASS__ . '#' . $this->id, $js); + } + } + + /** + * Renders the header cell content. + * This method will render a checkbox in the header when {@link selectableRows} is greater than 1 + * or in case {@link selectableRows} is null when {@link CGridView::selectableRows} is greater than 1. + */ + protected function renderHeaderCellContent() + { + if (trim($this->headerTemplate) === '') { + echo $this->grid->blankDisplay; + return; + } + + $item = ''; + if ($this->selectableRows === null && $this->grid->selectableRows > 1) { + $item = CHtml::checkBox($this->id . '_all', false, array('class' => 'select-on-check-all')); + } elseif ($this->selectableRows > 1) { + $item = CHtml::checkBox($this->id . '_all', false); + } else { + ob_start(); + parent::renderHeaderCellContent(); + $item = ob_get_clean(); + } + + echo strtr($this->headerTemplate, array( + '{item}' => $item, + )); + } + + /** + * Renders the data cell content. + * This method renders a checkbox in the data cell. + * @param integer $row the row number (zero-based) + * @param mixed $data the data associated with the row + */ + protected function renderDataCellContent($row, $data) + { + if ($this->value !== null) { + $value = $this->evaluateExpression($this->value, array('data' => $data, 'row' => $row)); + } elseif ($this->name !== null) { + $value = CHtml::value($data, $this->name); + } else { + $value = $this->grid->dataProvider->keys[$row]; + } + + $checked = false; + if ($this->checked !== null) { + $checked = $this->evaluateExpression($this->checked, array('data' => $data, 'row' => $row)); + } + + $options = $this->checkBoxHtmlOptions; + if ($this->disabled !== null) { + $options['disabled'] = $this->evaluateExpression($this->disabled, array('data' => $data, 'row' => $row)); + } + + $name = $options['name']; + unset($options['name']); + $options['value'] = $value; + $options['id'] = $this->id . '_' . $row; + echo CHtml::checkBox($name, $checked, $options); + } +} diff --git a/framework/yii/widgets/grid/Column.php b/framework/yii/widgets/grid/Column.php new file mode 100644 index 0000000..939904a --- /dev/null +++ b/framework/yii/widgets/grid/Column.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\widgets\grid; + +use Closure; +use yii\base\Object; +use yii\helpers\Html; +use yii\widgets\GridView; + +/** + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class Column extends Object +{ + /** + * @var string the ID of this column. This value should be unique among all grid view columns. + * If this is not set, it will be assigned one automatically. + */ + public $id; + /** + * @var GridView the grid view object that owns this column. + */ + public $grid; + /** + * @var string the header cell content. Note that it will not be HTML-encoded. + */ + public $header; + /** + * @var string the footer cell content. Note that it will not be HTML-encoded. + */ + public $footer; + /** + * @var callable + */ + public $content; + /** + * @var boolean whether this column is visible. Defaults to true. + */ + public $visible = true; + public $options = array(); + public $headerOptions = array(); + /** + * @var array|\Closure + */ + public $bodyOptions = array(); + public $footerOptions = array(); + /** + * @var array the HTML attributes for the filter cell tag. + */ + public $filterOptions=array(); + + + /** + * Renders the header cell. + */ + public function renderHeaderCell() + { + return Html::tag('th', $this->renderHeaderCellContent(), $this->headerOptions); + } + + /** + * Renders the footer cell. + */ + public function renderFooterCell() + { + return Html::tag('td', $this->renderFooterCellContent(), $this->footerOptions); + } + + /** + * Renders a data cell. + * @param mixed $model the data model being rendered + * @param integer $index the zero-based index of the data item among the item array returned by [[dataProvider]]. + * @return string the rendering result + */ + public function renderDataCell($model, $index) + { + if ($this->bodyOptions instanceof Closure) { + $options = call_user_func($this->bodyOptions, $model, $index, $this); + } else { + $options = $this->bodyOptions; + } + return Html::tag('td', $this->renderDataCellContent($model, $index), $options); + } + + /** + * Renders the filter cell. + */ + public function renderFilterCell() + { + return Html::tag('td', $this->renderFilterCellContent(), $this->filterOptions); + } + + /** + * Renders the header cell content. + * The default implementation simply renders {@link header}. + * This method may be overridden to customize the rendering of the header cell. + * @return string the rendering result + */ + protected function renderHeaderCellContent() + { + return trim($this->header) !== '' ? $this->header : $this->grid->emptyCell; + } + + /** + * Renders the footer cell content. + * The default implementation simply renders {@link footer}. + * This method may be overridden to customize the rendering of the footer cell. + * @return string the rendering result + */ + protected function renderFooterCellContent() + { + return trim($this->footer) !== '' ? $this->footer : $this->grid->emptyCell; + } + + /** + * Renders the data cell content. + * @param mixed $model the data model + * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]]. + * @return string the rendering result + */ + protected function renderDataCellContent($model, $index) + { + if ($this->content !== null) { + return call_user_func($this->content, $model, $index, $this); + } else { + return $this->grid->emptyCell; + } + } + + /** + * Renders the filter cell content. + * The default implementation simply renders a space. + * This method may be overridden to customize the rendering of the filter cell (if any). + * @return string the rendering result + */ + protected function renderFilterCellContent() + { + return $this->grid->emptyCell; + } +} diff --git a/framework/yii/widgets/grid/DataColumn.php b/framework/yii/widgets/grid/DataColumn.php new file mode 100644 index 0000000..ac65c4c --- /dev/null +++ b/framework/yii/widgets/grid/DataColumn.php @@ -0,0 +1,94 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\widgets\grid; +use yii\base\InvalidConfigException; +use yii\base\Model; +use yii\data\ActiveDataProvider; +use yii\db\ActiveQuery; +use yii\helpers\ArrayHelper; +use yii\helpers\Html; +use yii\helpers\Inflector; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class DataColumn extends Column +{ + public $attribute; + public $value; + public $format; + /** + * @var boolean whether to allow sorting by this column. If true and [[attribute]] is found in + * the sort definition of [[GridView::dataProvider]], then the header cell of this column + * will contain a link that may trigger the sorting when being clicked. + */ + public $enableSorting = true; + /** + * @var string|array|boolean the HTML code representing a filter input (eg a text field, a dropdown list) + * that is used for this data column. This property is effective only when + * {@link CGridView::filter} is set. + * If this property is not set, a text field will be generated as the filter input; + * If this property is an array, a dropdown list will be generated that uses this property value as + * the list options. + * If you don't want a filter for this data column, set this value to false. + */ + public $filter; + + + protected function renderHeaderCellContent() + { + if ($this->attribute !== null && $this->header === null) { + $provider = $this->grid->dataProvider; + if ($this->enableSorting && ($sort = $provider->getSort()) !== false && $sort->hasAttribute($this->attribute)) { + return $sort->link($this->attribute); + } + $models = $provider->getModels(); + if (($model = reset($models)) instanceof Model) { + /** @var Model $model */ + return $model->getAttributeLabel($this->attribute); + } elseif ($provider instanceof ActiveDataProvider) { + if ($provider->query instanceof ActiveQuery) { + /** @var Model $model */ + $model = new $provider->query->modelClass; + return $model->getAttributeLabel($this->attribute); + } + } + return Inflector::camel2words($this->attribute); + } else { + return parent::renderHeaderCellContent(); + } + } + + protected function renderFilterCellContent() + { + if (is_string($this->filter)) { + return $this->filter; + } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model && $this->attribute !== null) { + if (is_array($this->filter)) { + return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, array('prompt' => '')); + } else { + return Html::activeTextInput($this->grid->filterModel, $this->attribute); + } + } else { + return parent::renderFilterCellContent(); + } + } + + protected function renderDataCellContent($model, $index) + { + if ($this->value !== null) { + $value = call_user_func($this->value, $model, $index, $this); + } elseif ($this->content === null && $this->attribute !== null) { + $value = ArrayHelper::getValue($model, $this->attribute); + } else { + return parent::renderDataCellContent($model, $index); + } + return $this->grid->formatter->format($value, $this->format); + } +} diff --git a/framework/yii/widgets/grid/SerialColumn.php b/framework/yii/widgets/grid/SerialColumn.php new file mode 100644 index 0000000..b9b78a7 --- /dev/null +++ b/framework/yii/widgets/grid/SerialColumn.php @@ -0,0 +1,32 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\widgets\grid; + +/** + * SerialColumn displays a column of row numbers (1-based). + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class SerialColumn extends Column +{ + /** + * Renders the data cell content. + * @param mixed $model the data model + * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]]. + * @return string the rendering result + */ + protected function renderDataCellContent($model, $index) + { + $pagination = $this->grid->dataProvider->getPagination(); + if ($pagination !== false) { + return $pagination->getOffset() + $index + 1; + } else { + return $index + 1; + } + } +} diff --git a/tests/unit/data/base/Singer.php b/tests/unit/data/base/Singer.php index f1b91e1..5e9111d 100644 --- a/tests/unit/data/base/Singer.php +++ b/tests/unit/data/base/Singer.php @@ -15,7 +15,7 @@ class Singer extends Model return array( array('lastName', 'default', 'value' => 'Lennon'), array('lastName', 'required'), - array('underscore_style', 'yii\validators\CaptchaValidator'), + array('underscore_style', 'yii\captcha\CaptchaValidator'), ); } } diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index b851ae1..01dd682 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -189,4 +189,14 @@ class FormatterTest extends TestCase $this->assertSame("123123,12", $this->formatter->asNumber($value, 2)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asNumber(null)); } + + public function testFormat() + { + $value = time(); + $this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, 'date')); + $this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, 'DATE')); + $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, array('date', 'Y-m-d'))); + $this->setExpectedException('\yii\base\InvalidParamException'); + $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data')); + } } diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php index 7df2983..2699a52 100644 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ b/tests/unit/framework/data/ActiveDataProviderTest.php @@ -30,7 +30,7 @@ class ActiveDataProviderTest extends DatabaseTestCase $provider = new ActiveDataProvider(array( 'query' => Order::find()->orderBy('id'), )); - $orders = $provider->getItems(); + $orders = $provider->getModels(); $this->assertEquals(3, count($orders)); $this->assertTrue($orders[0] instanceof Order); $this->assertEquals(array(1, 2, 3), $provider->getKeys()); @@ -41,7 +41,7 @@ class ActiveDataProviderTest extends DatabaseTestCase 'pageSize' => 2, ) )); - $orders = $provider->getItems(); + $orders = $provider->getModels(); $this->assertEquals(2, count($orders)); } @@ -52,7 +52,7 @@ class ActiveDataProviderTest extends DatabaseTestCase 'db' => $this->getConnection(), 'query' => $query->from('tbl_order')->orderBy('id'), )); - $orders = $provider->getItems(); + $orders = $provider->getModels(); $this->assertEquals(3, count($orders)); $this->assertTrue(is_array($orders[0])); $this->assertEquals(array(0, 1, 2), $provider->getKeys()); @@ -65,7 +65,22 @@ class ActiveDataProviderTest extends DatabaseTestCase 'pageSize' => 2, ) )); - $orders = $provider->getItems(); + $orders = $provider->getModels(); $this->assertEquals(2, count($orders)); } + + public function testRefresh() + { + $query = new Query; + $provider = new ActiveDataProvider(array( + 'db' => $this->getConnection(), + 'query' => $query->from('tbl_order')->orderBy('id'), + )); + $this->assertEquals(3, count($provider->getModels())); + + $provider->getPagination()->pageSize = 2; + $this->assertEquals(3, count($provider->getModels())); + $provider->refresh(); + $this->assertEquals(2, count($provider->getModels())); + } }