Browse Source

Merge branch 'master' of github.com:yiisoft/yii2 into add-tests

tags/2.0.0-beta
Suralc 11 years ago
parent
commit
f78a77dfcf
  1. 2
      apps/advanced/backend/views/layouts/main.php
  2. 12
      apps/advanced/backend/web/css/site.css
  3. 2
      apps/advanced/frontend/controllers/SiteController.php
  4. 2
      apps/advanced/frontend/views/emails/passwordResetToken.php
  5. 2
      apps/advanced/frontend/views/layouts/main.php
  6. 2
      apps/advanced/frontend/views/site/contact.php
  7. 12
      apps/advanced/frontend/web/css/site.css
  8. 2
      apps/basic/controllers/SiteController.php
  9. 2
      apps/basic/views/layouts/main.php
  10. 2
      apps/basic/views/site/contact.php
  11. 12
      apps/basic/web/css/site.css
  12. 3
      docs/guide/controller.md
  13. 19
      docs/guide/installation.md
  14. 10
      docs/guide/overview.md
  15. 2
      docs/guide/performance.md
  16. 81
      docs/guide/security.md
  17. 2
      extensions/twig/composer.json
  18. 2
      framework/yii/assets.php
  19. 2
      framework/yii/assets/yii.captcha.js
  20. 13
      framework/yii/base/Application.php
  21. 44
      framework/yii/base/Component.php
  22. 5
      framework/yii/base/ErrorHandler.php
  23. 30
      framework/yii/base/Formatter.php
  24. 4
      framework/yii/base/Model.php
  25. 8
      framework/yii/base/Module.php
  26. 24
      framework/yii/base/Object.php
  27. 2
      framework/yii/bootstrap/Carousel.php
  28. 6
      framework/yii/bootstrap/Collapse.php
  29. 1
      framework/yii/bootstrap/Nav.php
  30. 2
      framework/yii/bootstrap/NavBar.php
  31. 2
      framework/yii/bootstrap/Progress.php
  32. 2
      framework/yii/bootstrap/Tabs.php
  33. 5
      framework/yii/captcha/Captcha.php
  34. 17
      framework/yii/captcha/CaptchaAction.php
  35. 3
      framework/yii/captcha/CaptchaAsset.php
  36. 6
      framework/yii/captcha/CaptchaValidator.php
  37. 0
      framework/yii/captcha/SpicyRice.md
  38. 0
      framework/yii/captcha/SpicyRice.ttf
  39. 16
      framework/yii/classes.php
  40. 2
      framework/yii/console/controllers/HelpController.php
  41. 105
      framework/yii/data/ActiveDataProvider.php
  42. 96
      framework/yii/data/ArrayDataProvider.php
  43. 6
      framework/yii/data/DataProvider.php
  44. 20
      framework/yii/data/IDataProvider.php
  45. 13
      framework/yii/data/Sort.php
  46. 8
      framework/yii/db/ActiveQuery.php
  47. 14
      framework/yii/db/ActiveRecord.php
  48. 12
      framework/yii/db/Command.php
  49. 4
      framework/yii/db/Connection.php
  50. 2
      framework/yii/debug/panels/LogPanel.php
  51. 4
      framework/yii/debug/panels/ProfilingPanel.php
  52. 17
      framework/yii/debug/panels/RequestPanel.php
  53. 5
      framework/yii/helpers/HtmlBase.php
  54. 5
      framework/yii/helpers/SecurityBase.php
  55. 2
      framework/yii/requirements/requirements.php
  56. 2
      framework/yii/validators/Validator.php
  57. 10
      framework/yii/views/errorHandler/callStackItem.php
  58. 23
      framework/yii/views/errorHandler/error.php
  59. 47
      framework/yii/views/errorHandler/exception.php
  60. 12
      framework/yii/views/errorHandler/previousException.php
  61. 2
      framework/yii/web/Request.php
  62. 1
      framework/yii/web/Response.php
  63. 6
      framework/yii/widgets/DetailView.php
  64. 328
      framework/yii/widgets/GridView.php
  65. 28
      framework/yii/widgets/ListView.php
  66. 9
      framework/yii/widgets/ListViewBase.php
  67. 191
      framework/yii/widgets/grid/CheckboxColumn.php
  68. 147
      framework/yii/widgets/grid/Column.php
  69. 94
      framework/yii/widgets/grid/DataColumn.php
  70. 32
      framework/yii/widgets/grid/SerialColumn.php
  71. 2
      tests/unit/data/base/Singer.php
  72. 10
      tests/unit/framework/base/FormatterTest.php
  73. 23
      tests/unit/framework/data/ActiveDataProviderTest.php

2
apps/advanced/backend/views/layouts/main.php

@ -24,7 +24,7 @@ AppAsset::register($this);
<div class="masthead"> <div class="masthead">
<h3 class="muted">My Company</h3> <h3 class="muted">My Company</h3>
<div class="navbar"> <div class="navbar fullwidth">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<?php echo Menu::widget(array( <?php echo Menu::widget(array(

12
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 */ /* Customize the navbar links to be fill the entire space of the .navbar */
.navbar .navbar-inner { .navbar.fullwidth .navbar-inner {
padding: 0; padding: 0;
} }
.navbar .nav { .navbar.fullwidth .nav {
margin: 0; margin: 0;
display: table; display: table;
width: 100%; width: 100%;
} }
.navbar .nav li { .navbar.fullwidth .nav li {
display: table-cell; display: table-cell;
width: 1%; width: 1%;
float: none; float: none;
} }
.navbar .nav li a { .navbar.fullwidth .nav li a {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
border-left: 1px solid rgba(255, 255, 255, .75); border-left: 1px solid rgba(255, 255, 255, .75);
border-right: 1px solid rgba(0, 0, 0, .1); 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-left: 0;
border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px;
} }
.navbar .nav li:last-child a { .navbar.fullwidth .nav li:last-child a {
border-right: 0; border-right: 0;
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
} }

2
apps/advanced/frontend/controllers/SiteController.php

@ -16,7 +16,7 @@ class SiteController extends Controller
{ {
return array( return array(
'captcha' => array( 'captcha' => array(
'class' => 'yii\web\CaptchaAction', 'class' => 'yii\captcha\CaptchaAction',
), ),
); );
} }

2
apps/advanced/frontend/views/emails/passwordResetToken.php

@ -6,7 +6,7 @@ use yii\helpers\Html;
* @var common\models\User $user; * @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)?>, Hello <?php echo Html::encode($user->username)?>,

2
apps/advanced/frontend/views/layouts/main.php

@ -25,7 +25,7 @@ AppAsset::register($this);
<div class="masthead"> <div class="masthead">
<h3 class="muted">My Company</h3> <h3 class="muted">My Company</h3>
<div class="navbar"> <div class="navbar fullwidth">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<?php <?php

2
apps/advanced/frontend/views/site/contact.php

@ -1,7 +1,7 @@
<?php <?php
use yii\helpers\Html; use yii\helpers\Html;
use yii\widgets\ActiveForm; use yii\widgets\ActiveForm;
use yii\widgets\Captcha; use yii\captcha\Captcha;
/** /**
* @var yii\base\View $this * @var yii\base\View $this

12
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 */ /* Customize the navbar links to be fill the entire space of the .navbar */
.navbar .navbar-inner { .navbar.fullwidth .navbar-inner {
padding: 0; padding: 0;
} }
.navbar .nav { .navbar.fullwidth .nav {
margin: 0; margin: 0;
display: table; display: table;
width: 100%; width: 100%;
} }
.navbar .nav li { .navbar.fullwidth .nav li {
display: table-cell; display: table-cell;
width: 1%; width: 1%;
float: none; float: none;
} }
.navbar .nav li a { .navbar.fullwidth .nav li a {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
border-left: 1px solid rgba(255, 255, 255, .75); border-left: 1px solid rgba(255, 255, 255, .75);
border-right: 1px solid rgba(0, 0, 0, .1); 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-left: 0;
border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px;
} }
.navbar .nav li:last-child a { .navbar.fullwidth .nav li:last-child a {
border-right: 0; border-right: 0;
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
} }

2
apps/basic/controllers/SiteController.php

@ -13,7 +13,7 @@ class SiteController extends Controller
{ {
return array( return array(
'captcha' => array( 'captcha' => array(
'class' => 'yii\web\CaptchaAction', 'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_DEV ? 'testme' : null, 'fixedVerifyCode' => YII_ENV_DEV ? 'testme' : null,
), ),
); );

2
apps/basic/views/layouts/main.php

@ -23,7 +23,7 @@ app\config\AppAsset::register($this);
<div class="masthead"> <div class="masthead">
<h3 class="muted">My Company</h3> <h3 class="muted">My Company</h3>
<div class="navbar"> <div class="navbar fullwidth">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<?php echo Menu::widget(array( <?php echo Menu::widget(array(

2
apps/basic/views/site/contact.php

@ -1,7 +1,7 @@
<?php <?php
use yii\helpers\Html; use yii\helpers\Html;
use yii\widgets\ActiveForm; use yii\widgets\ActiveForm;
use yii\widgets\Captcha; use yii\captcha\Captcha;
/** /**
* @var yii\base\View $this * @var yii\base\View $this

12
apps/basic/web/css/site.css

@ -44,35 +44,35 @@ body {
} }
/* Customize the navbar links to be fill the entire space of the .navbar */ /* Customize the navbar links to be fill the entire space of the .navbar */
.navbar .navbar-inner { .navbar.fullwidth .navbar-inner {
padding: 0; padding: 0;
} }
.navbar .nav { .navbar.fullwidth .nav {
margin: 0; margin: 0;
display: table; display: table;
width: 100%; width: 100%;
} }
.navbar .nav li { .navbar.fullwidth .nav li {
display: table-cell; display: table-cell;
width: 1%; width: 1%;
float: none; float: none;
} }
.navbar .nav li a { .navbar.fullwidth .nav li a {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
border-left: 1px solid rgba(255, 255, 255, .75); border-left: 1px solid rgba(255, 255, 255, .75);
border-right: 1px solid rgba(0, 0, 0, .1); 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-left: 0;
border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px;
} }
.navbar .nav li:last-child a { .navbar.fullwidth .nav li:last-child a {
border-right: 0; border-right: 0;
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
} }

3
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. 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 ### Defaults
If user isn't specifying any route i.e. using URL like `http://example.com/`, Yii assumes that default route should be If user isn't specifying any route i.e. using URL like `http://example.com/`, Yii assumes that default route should be

19
docs/guide/installation.md

@ -1,7 +1,23 @@
Installation 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/). 1. Download Yii Framework from [yiiframework.com](http://www.yiiframework.com/).
2. Unpack the Yii release file to a Web-accessible directory. 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 Yii, should be protected from Web access; otherwise they might be exploited
by hackers. by hackers.
Requirements Requirements
------------ ------------

10
docs/guide/overview.md

@ -31,6 +31,10 @@ management systems (CMS), e-commerce systems, etc.
How does Yii Compare with Other Frameworks? How does Yii Compare with Other Frameworks?
------------------------------------------- -------------------------------------------
Like most PHP frameworks, Yii is an MVC (Model-View-Controller) framework. - 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.
TBD - 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.

2
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 cache storage such as memcached has no guarantee that session data will not
be lost leading to unexpected logouts. 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 Improving application
--------------------- ---------------------

81
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

2
extensions/twig/composer.json

@ -20,7 +20,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"twig/twig": "v1.13.0" "twig/twig": "v1.13.2"
}, },
"autoload": { "autoload": {
"psr-0": { "yii\\twig": "" } "psr-0": { "yii\\twig": "" }

2
framework/yii/assets.php

@ -6,6 +6,6 @@ return array(
yii\validators\PunycodeAsset::className(), yii\validators\PunycodeAsset::className(),
yii\validators\ValidationAsset::className(), yii\validators\ValidationAsset::className(),
yii\widgets\ActiveFormAsset::className(), yii\widgets\ActiveFormAsset::className(),
yii\widgets\CaptchaAsset::className(), yii\captcha\CaptchaAsset::className(),
yii\widgets\MaskedInputAsset::className(), yii\widgets\MaskedInputAsset::className(),
); );

2
framework/yii/assets/yii.captcha.js

@ -1,7 +1,7 @@
/** /**
* Yii Captcha widget. * 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/ * @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC * @copyright Copyright (c) 2008 Yii Software LLC

13
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. * Runs the application.
* This is the main entrance of an application. * This is the main entrance of an application.
* @return integer the exit status (0 means normal, non-zero values mean abnormal) * @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() public function handleFatalError()
{ {
unset($this->_memoryReserve);
// load ErrorException manually here because autoloading them will not work // load ErrorException manually here because autoloading them will not work
// when error occurs while autoloading a class // when error occurs while autoloading a class
if (!class_exists('\\yii\\base\\Exception', false)) { if (!class_exists('\\yii\\base\\Exception', false)) {
@ -487,7 +499,6 @@ abstract class Application extends Module
$error = error_get_last(); $error = error_get_last();
if (ErrorException::isFatalError($error)) { if (ErrorException::isFatalError($error)) {
unset($this->_memoryReserve);
$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); $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 // use error_log because it's too late to use Yii log
error_log($exception); error_log($exception);

44
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 * - the class has a getter or setter method associated with the specified name
* (in this case, property name is case-insensitive); * (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);
* - an attached behavior has a property of the given name (when `$checkBehavior` is true). * - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
* *
* @param string $name the property name * @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
* @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return boolean whether the property is defined * @return boolean whether the property is defined
* @see canGetProperty * @see canGetProperty
* @see canSetProperty * @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 * - the class has a getter method associated with the specified name
* (in this case, property name is case-insensitive); * (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);
* - an attached behavior has a readable property of the given name (when `$checkBehavior` is true). * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true).
* *
* @param string $name the property name * @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
* @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return boolean whether the property can be read * @return boolean whether the property can be read
* @see canSetProperty * @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; return true;
} else { } elseif ($checkBehaviors) {
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name, $checkVar)) { if ($behavior->canGetProperty($name, $checkVars)) {
return true; return true;
} }
} }
@ -271,23 +271,23 @@ class Component extends Object
* *
* - the class has a setter method associated with the specified name * - the class has a setter method associated with the specified name
* (in this case, property name is case-insensitive); * (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);
* - an attached behavior has a writable property of the given name (when `$checkBehavior` is true). * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true).
* *
* @param string $name the property name * @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
* @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return boolean whether the property can be written * @return boolean whether the property can be written
* @see canGetProperty * @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; return true;
} else { } elseif ($checkBehaviors) {
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name, $checkVar)) { if ($behavior->canSetProperty($name, $checkVars)) {
return true; return true;
} }
} }

5
framework/yii/base/ErrorHandler.php

@ -192,11 +192,16 @@ class ErrorHandler extends Component
*/ */
public function renderFile($_file_, $_params_) public function renderFile($_file_, $_params_)
{ {
$_params_['handler'] = $this;
if ($this->exception instanceof ErrorException) {
ob_start(); ob_start();
ob_implicit_flush(false); ob_implicit_flush(false);
extract($_params_, EXTR_OVERWRITE); extract($_params_, EXTR_OVERWRITE);
require(Yii::getAlias($_file_)); require(Yii::getAlias($_file_));
return ob_get_clean(); return ob_get_clean();
} else {
return Yii::$app->getView()->renderFile($_file_, $_params_, $this);
}
} }
/** /**

30
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. * 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", * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
* then [[asHtml()]] will be used. Type names are case insensitive. * then [[asHtml()]] will be used. Format names are case insensitive.
* @param mixed $value the value to be formatted * @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 * @return string the formatting result
* @throws InvalidParamException if the type is not supported by this class. * @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)) { if (method_exists($this, $method)) {
return $this->$method($value); return call_user_func_array(array($this, $method), $params);
} else { } else {
throw new InvalidParamException("Unknown type: $type"); throw new InvalidParamException("Unknown type: $format");
} }
} }

4
framework/yii/base/Model.php

@ -117,8 +117,8 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
* array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'), * array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'),
* // an inline validator defined via the "authenticate()" method in the model class * // an inline validator defined via the "authenticate()" method in the model class
* array('password', 'authenticate', 'on' => 'login'), * array('password', 'authenticate', 'on' => 'login'),
* // a validator of class "CaptchaValidator" * // a validator of class "DateRangeValidator"
* array('captcha', 'CaptchaValidator'), * array('dateRange', 'DateRangeValidator'),
* ); * );
* ~~~ * ~~~
* *

8
framework/yii/base/Module.php

@ -192,13 +192,7 @@ abstract class Module extends Component
*/ */
public function getUniqueId() public function getUniqueId()
{ {
if ($this instanceof Application) { return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id;
return '';
} elseif ($this->module) {
return ltrim($this->module->getUniqueId() . '/' . $this->id, '/');
} else {
return $this->id;
}
} }
/** /**

24
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 * - the class has a getter or setter method associated with the specified name
* (in this case, property name is case-insensitive); * (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 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 * @return boolean whether the property is defined
* @see canGetProperty * @see canGetProperty
* @see canSetProperty * @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 * - the class has a getter method associated with the specified name
* (in this case, property name is case-insensitive); * (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 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 * @return boolean whether the property can be read
* @see canSetProperty * @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 * - the class has a setter method associated with the specified name
* (in this case, property name is case-insensitive); * (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 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 * @return boolean whether the property can be written
* @see canGetProperty * @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);
} }
/** /**

2
framework/yii/bootstrap/Carousel.php

@ -55,7 +55,7 @@ class Carousel extends Widget
* // required, slide content (HTML), such as an image tag * // required, slide content (HTML), such as an image tag
* 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>', * 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>',
* // optional, the caption (HTML) of the slide * // 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 * // optional the HTML attributes of the slide container
* 'options' => array(), * 'options' => array(),
* ) * )

6
framework/yii/bootstrap/Collapse.php

@ -23,7 +23,7 @@ use yii\helpers\Html;
* 'Collapsible Group Item #1' => array( * 'Collapsible Group Item #1' => array(
* 'content' => 'Anim pariatur cliche...', * 'content' => 'Anim pariatur cliche...',
* // open its content by default * // open its content by default
* 'contentOptions' => array('class'=>'in') * 'contentOptions' => array('class' => 'in')
* ), * ),
* // another group item * // another group item
* 'Collapsible Group Item #2' => array( * 'Collapsible Group Item #2' => array(
@ -51,9 +51,9 @@ class Collapse extends Widget
* // required, the content (HTML) of the group * // required, the content (HTML) of the group
* 'content' => 'Anim pariatur cliche...', * 'content' => 'Anim pariatur cliche...',
* // optional the HTML attributes of the content group * // optional the HTML attributes of the content group
* 'contentOptions'=> array(), * 'contentOptions' => array(),
* // optional the HTML attributes of the group * // optional the HTML attributes of the group
* 'options'=> array(), * 'options' => array(),
* ) * )
* ``` * ```
*/ */

1
framework/yii/bootstrap/Nav.php

@ -136,6 +136,7 @@ class Nav extends Widget
if (is_array($items)) { if (is_array($items)) {
$items = Dropdown::widget(array( $items = Dropdown::widget(array(
'items' => $items, 'items' => $items,
'encodeLabels' => $this->encodeLabels,
'clientOptions' => false, 'clientOptions' => false,
)); ));
} }

2
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. * // optional, the menu item class type of the widget to render. Defaults to "Nav" widget.
* 'class' => 'Menu item class type', * 'class' => 'Menu item class type',
* // required, the configuration options of the widget. * // required, the configuration options of the widget.
* 'options'=> array(...), * 'options' => array(...),
* ), * ),
* // optionally, you can pass a string * // optionally, you can pass a string
* '<form class="navbar-search pull-left" action="">' . * '<form class="navbar-search pull-left" action="">' .

2
framework/yii/bootstrap/Progress.php

@ -47,7 +47,7 @@ use yii\helpers\Html;
* echo Progress::widget(array( * echo Progress::widget(array(
* 'bars' => array( * 'bars' => array(
* array('percent' => 30, 'options' => array('class' => 'bar-danger')), * 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')) * array('percent' => 35, 'options' => array('class' => 'bar-warning'))
* ) * )
* )); * ));

2
framework/yii/bootstrap/Tabs.php

@ -28,7 +28,7 @@ use yii\helpers\Html;
* 'label' => 'Two', * 'label' => 'Two',
* 'content' => 'Anim pariatur cliche...', * 'content' => 'Anim pariatur cliche...',
* 'headerOptions' => array(...), * 'headerOptions' => array(...),
* 'options' => array('id'=>'myveryownID'), * 'options' => array('id' => 'myveryownID'),
* ), * ),
* array( * array(
* 'label' => 'Dropdown', * 'label' => 'Dropdown',

5
framework/yii/widgets/Captcha.php → framework/yii/captcha/Captcha.php

@ -5,13 +5,14 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\widgets; namespace yii\captcha;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\helpers\Html; use yii\helpers\Html;
use yii\helpers\Json; 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. * Captcha renders a CAPTCHA image and an input field that takes user-entered verification code.

17
framework/yii/web/CaptchaAction.php → framework/yii/captcha/CaptchaAction.php

@ -5,12 +5,11 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\web; namespace yii\captcha;
use Yii; use Yii;
use yii\base\Action; use yii\base\Action;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\widgets\Captcha;
/** /**
* CaptchaAction renders a CAPTCHA image. * 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. * @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, * @var string the fixed verification code. When this property is set,
* [[getVerifyCode()]] will always return the value of this property. * [[getVerifyCode()]] will always return the value of this property.
@ -116,12 +115,14 @@ class CaptchaAction extends Action
if (isset($_GET[self::REFRESH_GET_VAR])) { if (isset($_GET[self::REFRESH_GET_VAR])) {
// AJAX request for regenerating code // AJAX request for regenerating code
$code = $this->getVerifyCode(true); $code = $this->getVerifyCode(true);
/** @var \yii\web\Controller $controller */
$controller = $this->controller;
return json_encode(array( return json_encode(array(
'hash1' => $this->generateValidationHash($code), 'hash1' => $this->generateValidationHash($code),
'hash2' => $this->generateValidationHash(strtolower($code)), 'hash2' => $this->generateValidationHash(strtolower($code)),
// we add a random 'v' parameter so that FireFox can refresh the image // we add a random 'v' parameter so that FireFox can refresh the image
// when src attribute of image tag is changed // 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 { } else {
$this->setHttpHeaders(); $this->setHttpHeaders();
@ -153,7 +154,7 @@ class CaptchaAction extends Action
return $this->fixedVerifyCode; return $this->fixedVerifyCode;
} }
$session = Yii::$app->session; $session = Yii::$app->getSession();
$session->open(); $session->open();
$name = $this->getSessionKey(); $name = $this->getSessionKey();
if ($session[$name] === null || $regenerate) { if ($session[$name] === null || $regenerate) {
@ -189,15 +190,15 @@ class CaptchaAction extends Action
*/ */
protected function generateVerifyCode() protected function generateVerifyCode()
{ {
if ($this->minLength > $this->maxLength) {
$this->maxLength = $this->minLength;
}
if ($this->minLength < 3) { if ($this->minLength < 3) {
$this->minLength = 3; $this->minLength = 3;
} }
if ($this->maxLength > 20) { if ($this->maxLength > 20) {
$this->maxLength = 20; $this->maxLength = 20;
} }
if ($this->minLength > $this->maxLength) {
$this->maxLength = $this->minLength;
}
$length = mt_rand($this->minLength, $this->maxLength); $length = mt_rand($this->minLength, $this->maxLength);
$letters = 'bcdfghjklmnpqrstvwxyz'; $letters = 'bcdfghjklmnpqrstvwxyz';

3
framework/yii/widgets/CaptchaAsset.php → framework/yii/captcha/CaptchaAsset.php

@ -5,7 +5,8 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\widgets; namespace yii\captcha;
use yii\web\AssetBundle; use yii\web\AssetBundle;
/** /**

6
framework/yii/validators/CaptchaValidator.php → framework/yii/captcha/CaptchaValidator.php

@ -5,11 +5,13 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\validators; namespace yii\captcha;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\helpers\Html; 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. * 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. * Returns the CAPTCHA action object.
* @throws InvalidConfigException * @throws InvalidConfigException
* @return \yii\web\CaptchaAction the action object * @return \yii\captcha\CaptchaAction the action object
*/ */
public function getCaptchaAction() public function getCaptchaAction()
{ {

0
framework/yii/web/SpicyRice.md → framework/yii/captcha/SpicyRice.md

0
framework/yii/web/SpicyRice.ttf → framework/yii/captcha/SpicyRice.ttf

16
framework/yii/classes.php

@ -91,6 +91,10 @@ return array(
'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php', 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php',
'yii\caching\XCache' => YII_PATH . '/caching/XCache.php', 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php',
'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.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\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php',
'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php',
'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php',
@ -167,7 +171,6 @@ return array(
'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php', 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php',
'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php', 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php',
'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.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\CompareValidator' => YII_PATH . '/validators/CompareValidator.php',
'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php', 'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php',
'yii\validators\DefaultValueValidator' => YII_PATH . '/validators/DefaultValueValidator.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\AssetConverter' => YII_PATH . '/web/AssetConverter.php',
'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php', 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php',
'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.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\Controller' => YII_PATH . '/web/Controller.php',
'yii\web\Cookie' => YII_PATH . '/web/Cookie.php', 'yii\web\Cookie' => YII_PATH . '/web/Cookie.php',
'yii\web\CookieCollection' => YII_PATH . '/web/CookieCollection.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\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php',
'yii\widgets\Block' => YII_PATH . '/widgets/Block.php', 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php',
'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.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\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php',
'yii\widgets\DetailView' => YII_PATH . '/widgets/DetailView.php', 'yii\widgets\DetailView' => YII_PATH . '/widgets/DetailView.php',
'yii\widgets\FragmentCache' => YII_PATH . '/widgets/FragmentCache.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\InputWidget' => YII_PATH . '/widgets/InputWidget.php',
'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.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\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php',
'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php',
'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php',

2
framework/yii/console/controllers/HelpController.php

@ -129,7 +129,7 @@ class HelpController extends Controller
$files = scandir($module->getControllerPath()); $files = scandir($module->getControllerPath());
foreach ($files as $file) { foreach ($files as $file) {
if (strcmp(substr($file, -14), 'Controller.php') === 0) { 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));
} }
} }

105
framework/yii/data/ActiveDataProvider.php

@ -29,7 +29,7 @@ use yii\db\Connection;
* )); * ));
* *
* // get the posts in the current page * // get the posts in the current page
* $posts = $provider->getItems(); * $posts = $provider->getModels();
* ~~~ * ~~~
* *
* And the following example shows how to use ActiveDataProvider without ActiveRecord: * 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 * // get the posts in the current page
* $posts = $provider->getItems(); * $posts = $provider->getModels();
* ~~~ * ~~~
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
@ -53,18 +53,18 @@ use yii\db\Connection;
class ActiveDataProvider extends DataProvider 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. * if it is not explicitly set.
*/ */
public $query; public $query;
/** /**
* @var string|callable the column that is used as the key of the data items. * @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 item. * 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. * - 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() * @see getKeys()
*/ */
@ -75,9 +75,9 @@ class ActiveDataProvider extends DataProvider
*/ */
public $db; public $db;
private $_items; private $_models;
private $_keys; private $_keys;
private $_count; private $_totalCount;
/** /**
* Initializes the DbCache component. * Initializes the DbCache component.
@ -96,59 +96,55 @@ class ActiveDataProvider extends DataProvider
} }
/** /**
* Returns the number of data items in the current page. * Returns the number of data models in the current page.
* This is equivalent to `count($provider->items)`. * This is equivalent to `count($provider->models)`.
* When [[pagination]] is false, this is the same as [[totalCount]]. * When [[pagination]] is false, this is the same as [[totalCount]].
* @param boolean $refresh whether to recalculate the item count. If true, * @return integer the number of data models in the current page.
* this will cause re-fetching of [[items]].
* @return integer the number of data items 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]]. * When [[pagination]] is false, this returns the same value as [[count]].
* If [[totalCount]] is not explicitly set, it will be calculated * If [[totalCount]] is not explicitly set, it will be calculated
* using [[query]] with a COUNT query. * using [[query]] with a COUNT query.
* @param boolean $refresh whether to recalculate the item count * @return integer total number of possible data models.
* @return integer total number of possible data items.
* @throws InvalidConfigException * @throws InvalidConfigException
*/ */
public function getTotalCount($refresh = false) public function getTotalCount()
{ {
if ($this->getPagination() === false) { if ($this->getPagination() === false) {
return $this->getCount($refresh); return $this->getCount();
} elseif ($this->_count === null || $refresh) { } elseif ($this->_totalCount === null) {
if (!$this->query instanceof Query) { if (!$this->query instanceof Query) {
throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.');
} }
$query = clone $this->query; $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. * Sets the total number of data models.
* @param integer $value the total number of data items. * @param integer $value the total number of data models.
*/ */
public function setTotalCount($value) public function setTotalCount($value)
{ {
$this->_count = $value; $this->_totalCount = $value;
} }
/** /**
* Returns the data items in the current page. * Returns the data models in the current page.
* @param boolean $refresh whether to re-fetch the data items. * @return array the list of data models in the current page.
* @return array the list of data items in the current page. * @throws InvalidConfigException if [[query]] is not set or invalid.
* @throws InvalidConfigException
*/ */
public function getItems($refresh = false) public function getModels()
{ {
if ($this->_items === null || $refresh) { if ($this->_models === null) {
if (!$this->query instanceof Query) { if (!$this->query instanceof Query) {
throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); 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) { if (($sort = $this->getSort()) !== false) {
$this->query->orderBy($sort->getOrders()); $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. * Returns the key values associated with the data models.
* @param boolean $refresh whether to re-fetch the data items and re-calculate the keys * @return array the list of key values corresponding to [[models]]. Each data model in [[models]]
* @return array the list of key values corresponding to [[items]]. Each data item in [[items]]
* is uniquely identified by the corresponding key value in this array. * 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(); $this->_keys = array();
$items = $this->getItems($refresh); $models = $this->getModels();
if ($this->key !== null) { if ($this->key !== null) {
foreach ($items as $item) { foreach ($models as $model) {
if (is_string($this->key)) { if (is_string($this->key)) {
$this->_keys[] = $item[$this->key]; $this->_keys[] = $model[$this->key];
} else { } else {
$this->_keys[] = call_user_func($this->key, $item); $this->_keys[] = call_user_func($this->key, $model);
} }
} }
} elseif ($this->query instanceof ActiveQuery) { } elseif ($this->query instanceof ActiveQuery) {
@ -189,22 +184,34 @@ class ActiveDataProvider extends DataProvider
$pks = $class::primaryKey(); $pks = $class::primaryKey();
if (count($pks) === 1) { if (count($pks) === 1) {
$pk = $pks[0]; $pk = $pks[0];
foreach ($items as $item) { foreach ($models as $model) {
$this->_keys[] = $item[$pk]; $this->_keys[] = $model[$pk];
} }
} else { } else {
foreach ($items as $item) { foreach ($models as $model) {
$keys = array(); $keys = array();
foreach ($pks as $pk) { foreach ($pks as $pk) {
$keys[] = $item[$pk]; $keys[] = $model[$pk];
} }
$this->_keys[] = json_encode($keys); $this->_keys[] = json_encode($keys);
} }
} }
} else { } else {
$this->_keys = array_keys($items); $this->_keys = array_keys($models);
} }
} }
return $this->_keys; 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;
}
} }

96
framework/yii/data/ArrayDataProvider.php

@ -13,25 +13,25 @@ use yii\helpers\ArrayHelper;
/** /**
* ArrayDataProvider implements a data provider based on a data array. * 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. * ArrayDataProvider will provide the data after sorting and/or pagination.
* You may configure the [[sort]] and [[pagination]] properties to * You may configure the [[sort]] and [[pagination]] properties to
* customize the sorting and pagination behaviors. * 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). * or associative arrays (e.g. query results of DAO).
* Make sure to set the [[key]] property to the name of the field that uniquely * 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. * identifies a data record or false if you do not have such a field.
* *
* Compared to [[ActiveDataProvider]], ArrayDataProvider could be less efficient * 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: * ArrayDataProvider may be used in the following way:
* *
* ~~~ * ~~~
* $query = new Query; * $query = new Query;
* $provider = new ArrayDataProvider(array( * $provider = new ArrayDataProvider(array(
* 'allItems' => $query->from('tbl_post')->all(), * 'allModels' => $query->from('tbl_post')->all(),
* 'sort' => array( * 'sort' => array(
* 'attributes' => array( * 'attributes' => array(
* 'id', 'username', 'email', * 'id', 'username', 'email',
@ -42,7 +42,7 @@ use yii\helpers\ArrayHelper;
* ), * ),
* )); * ));
* // get the posts in the current page * // 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 * 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 class ArrayDataProvider extends DataProvider
{ {
/** /**
* @var string|callable the column that is used as the key of the data items. * @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 item. * 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 [[items]] array will be used. * If this is not set, the index of the [[models]] array will be used.
* @see getKeys() * @see getKeys()
*/ */
public $key; public $key;
/** /**
* @var array the data that is not paginated or sorted. When pagination is enabled, * @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. * The array elements must use zero-based integer keys.
*/ */
public $allItems; public $allModels;
private $_totalCount; private $_totalCount;
/** /**
* Returns the total number of data items. * Returns the total number of data models.
* @return integer total number of possible data items. * @return integer total number of possible data models.
* @throws InvalidConfigException
*/ */
public function getTotalCount() public function getTotalCount()
{ {
if ($this->getPagination() === false) { if ($this->getPagination() === false) {
return $this->getCount(); return $this->getCount();
} elseif ($this->_totalCount === null) { } elseif ($this->_totalCount === null) {
if ($this->allItems !== null) { $this->_totalCount = count($this->allModels);
$this->_totalCount = count($this->allItems);
} else {
throw new InvalidConfigException('Unable to determine total item count: either "allItems" or "totalCount" must be set.');
}
} }
return $this->_totalCount; return $this->_totalCount;
} }
/** /**
* Sets the total number of data items. * Sets the total number of data models.
* @param integer $value the total number of data items. * @param integer $value the total number of data models.
*/ */
public function setTotalCount($value) public function setTotalCount($value)
{ {
$this->_totalCount = $value; $this->_totalCount = $value;
} }
private $_items; private $_models;
/** /**
* Returns the data items in the current page. * Returns the data models in the current page.
* @return array the list of data items in the current page. * @return array the list of data models in the current page.
* @throws InvalidConfigException
*/ */
public function getItems() public function getModels()
{ {
if ($this->_items === null) { if ($this->_models === null) {
if (($items = $this->allItems) === null) { if (($models = $this->allModels) === null) {
throw new InvalidConfigException('Either "items" or "allItems" must be set.'); return array();
} }
if (($sort = $this->getSort()) !== false) { if (($sort = $this->getSort()) !== false) {
$items = $this->sortItems($items, $sort); $models = $this->sortModels($models, $sort);
} }
if (($pagination = $this->getPagination()) !== false) { if (($pagination = $this->getPagination()) !== false) {
$pagination->totalCount = $this->getTotalCount(); $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. * Sets the data models in the current page.
* @param array $items the items 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; private $_keys;
/** /**
* Returns the key values associated with the data items. * Returns the key values associated with the data models.
* @return array the list of key values corresponding to [[items]]. Each data item in [[items]] * @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. * is uniquely identified by the corresponding key value in this array.
*/ */
public function getKeys() public function getKeys()
{ {
if ($this->_keys === null) { if ($this->_keys === null) {
$this->_keys = array(); $this->_keys = array();
$items = $this->getItems(); $models = $this->getModels();
if ($this->key !== null) { if ($this->key !== null) {
foreach ($items as $item) { foreach ($models as $model) {
if (is_string($this->key)) { if (is_string($this->key)) {
$this->_keys[] = $item[$this->key]; $this->_keys[] = $model[$this->key];
} else { } else {
$this->_keys[] = call_user_func($this->key, $item); $this->_keys[] = call_user_func($this->key, $model);
} }
} }
} else { } else {
$this->_keys = array_keys($items); $this->_keys = array_keys($models);
} }
} }
return $this->_keys; return $this->_keys;
} }
/** /**
* Sets the key values associated with the data items. * Sets the key values associated with the data models.
* @param array $keys the list of key values corresponding to [[items]]. * @param array $keys the list of key values corresponding to [[models]].
*/ */
public function setKeys($keys) public function setKeys($keys)
{ {
@ -171,17 +165,17 @@ class ArrayDataProvider extends DataProvider
} }
/** /**
* Sorts the data items according to the given sort definition * Sorts the data models according to the given sort definition
* @param array $items the items to be sorted * @param array $models the models to be sorted
* @param Sort $sort the sort definition * @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(); $orders = $sort->getOrders();
if (!empty($orders)) { 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;
} }
} }

6
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. * Returns the number of data models in the current page.
* @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() public function getCount()
{ {
return count($this->getItems()); return count($this->getModels());
} }
} }

20
framework/yii/data/IDataProvider.php

@ -19,29 +19,29 @@ namespace yii\data;
interface IDataProvider interface IDataProvider
{ {
/** /**
* Returns the number of data items in the current page. * Returns the number of data models in the current page.
* This is equivalent to `count($provider->getItems())`. * This is equivalent to `count($provider->getModels())`.
* When [[pagination]] is false, this is the same as [[totalCount]]. * 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(); 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]]. * 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(); public function getTotalCount();
/** /**
* Returns the data items in the current page. * Returns the data models in the current page.
* @return array the list of data items 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. * Returns the key values associated with the data models.
* @return array the list of key values corresponding to [[items]]. Each data item in [[items]] * @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. * is uniquely identified by the corresponding key value in this array.
*/ */
public function getKeys(); public function getKeys();

13
framework/yii/data/Sort.php

@ -191,17 +191,18 @@ class Sort extends Object
{ {
$attributes = array(); $attributes = array();
foreach ($this->attributes as $name => $attribute) { foreach ($this->attributes as $name => $attribute) {
if (is_array($attribute)) { if (!is_array($attribute)) {
$attributes[$name] = $attribute;
if (!isset($attribute['label'])) {
$attributes[$name]['label'] = Inflector::camel2words($name);
}
} else {
$attributes[$attribute] = array( $attributes[$attribute] = array(
'asc' => array($attribute => self::ASC), 'asc' => array($attribute => self::ASC),
'desc' => array($attribute => self::DESC), 'desc' => array($attribute => self::DESC),
'label' => Inflector::camel2words($attribute), '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; $this->attributes = $attributes;

8
framework/yii/db/ActiveQuery.php

@ -124,10 +124,14 @@ class ActiveQuery extends Query
{ {
$command = $this->createCommand($db); $command = $this->createCommand($db);
$row = $command->queryOne(); $row = $command->queryOne();
if ($row !== false && !$this->asArray) { if ($row !== false) {
if ($this->asArray) {
$model = $row;
} else {
/** @var $class ActiveRecord */ /** @var $class ActiveRecord */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::create($row);
}
if (!empty($this->with)) { if (!empty($this->with)) {
$models = array($model); $models = array($model);
$this->populateRelations($models, $this->with); $this->populateRelations($models, $this->with);
@ -135,7 +139,7 @@ class ActiveQuery extends Query
} }
return $model; return $model;
} else { } else {
return $row === false ? null : $row; return null;
} }
} }

14
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. * Returns the old attribute values.
* @return array the old attribute values (name-value pairs) * @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 * @param string $class the class name to be namespaced
* @return string the namespaced class name * @return string the namespaced class name
*/ */
protected function getNamespacedClass($class) protected static function getNamespacedClass($class)
{ {
if (strpos($class, '\\') === false) { if (strpos($class, '\\') === false) {
$reflector = new \ReflectionClass($this); $reflector = new \ReflectionClass(static::className());
return $reflector->getNamespaceName() . '\\' . $class; return $reflector->getNamespaceName() . '\\' . $class;
} else { } else {
return $class; return $class;

12
framework/yii/db/Command.php

@ -146,9 +146,9 @@ class Command extends \yii\base\Component
try { try {
$this->pdoStatement = $this->db->pdo->prepare($sql); $this->pdoStatement = $this->db->pdo->prepare($sql);
} catch (\Exception $e) { } 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; $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; return $n;
} catch (\Exception $e) { } catch (\Exception $e) {
Yii::endProfile($token, __METHOD__); Yii::endProfile($token, __METHOD__);
$message = $e->getMessage(); $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
Yii::error("$message\nFailed to execute SQL: $rawSql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int)$e->getCode(), $e); throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
} }
@ -430,8 +427,7 @@ class Command extends \yii\base\Component
return $result; return $result;
} catch (\Exception $e) { } catch (\Exception $e) {
Yii::endProfile($token, __METHOD__); Yii::endProfile($token, __METHOD__);
$message = $e->getMessage(); $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
Yii::error("$message\nCommand::$method() failed: $rawSql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int)$e->getCode(), $e); throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
} }

4
framework/yii/db/Connection.php

@ -307,9 +307,7 @@ class Connection extends Component
Yii::endProfile($token, __METHOD__); Yii::endProfile($token, __METHOD__);
} catch (\PDOException $e) { } catch (\PDOException $e) {
Yii::endProfile($token, __METHOD__); Yii::endProfile($token, __METHOD__);
Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__); throw new Exception($e->getMessage(), $e->errorInfo, (int)$e->getCode(), $e);
$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);
} }
} }
} }

2
framework/yii/debug/panels/LogPanel.php

@ -55,7 +55,7 @@ EOD;
foreach ($this->data['messages'] as $log) { foreach ($this->data['messages'] as $log) {
list ($message, $level, $category, $time, $traces) = $log; list ($message, $level, $category, $time, $traces) = $log;
$time = date('H:i:s.', $time) . sprintf('%03d', (int)(($time - (int)$time) * 1000)); $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)) { if (!empty($traces)) {
$message .= Html::ul($traces, array( $message .= Html::ul($traces, array(
'class' => 'trace', 'class' => 'trace',

4
framework/yii/debug/panels/ProfilingPanel.php

@ -33,10 +33,10 @@ class ProfilingPanel extends Panel
return <<<EOD return <<<EOD
<div class="yii-debug-toolbar-block"> <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>
<div class="yii-debug-toolbar-block"> <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> </div>
EOD; EOD;
} }

17
framework/yii/debug/panels/RequestPanel.php

@ -12,6 +12,7 @@ use yii\base\InlineAction;
use yii\bootstrap\Tabs; use yii\bootstrap\Tabs;
use yii\debug\Panel; use yii\debug\Panel;
use yii\helpers\Html; use yii\helpers\Html;
use yii\web\Response;
/** /**
* Debugger panel that collects and displays request data. * Debugger panel that collects and displays request data.
@ -29,9 +30,24 @@ class RequestPanel extends Panel
public function getSummary() public function getSummary()
{ {
$url = $this->getUrl(); $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 return <<<EOD
<div class="yii-debug-toolbar-block"> <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> <a href="$url">Action: <span class="label">{$this->data['action']}</span></a>
</div> </div>
EOD; EOD;
@ -113,6 +129,7 @@ EOD;
$session = Yii::$app->getComponent('session', false); $session = Yii::$app->getComponent('session', false);
return array( return array(
'flashes' => $session ? $session->getAllFlashes() : array(), 'flashes' => $session ? $session->getAllFlashes() : array(),
'statusCode' => Yii::$app->getResponse()->getStatusCode(),
'requestHeaders' => $requestHeaders, 'requestHeaders' => $requestHeaders,
'responseHeaders' => $responseHeaders, 'responseHeaders' => $responseHeaders,
'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute, 'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute,

5
framework/yii/helpers/HtmlBase.php

@ -1339,11 +1339,12 @@ class HtmlBase
* *
* - is an empty string: the currently requested URL will be returned; * - 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 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. * 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 * - 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()]]. * 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')`. * 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 * @param array|string $url the parameter to be used to generate a valid URL
* @return string the normalized URL * @return string the normalized URL
@ -1355,7 +1356,7 @@ class HtmlBase
if (isset($url[0])) { if (isset($url[0])) {
$route = $url[0]; $route = $url[0];
$params = array_splice($url, 1); $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); return Yii::$app->controller->createUrl($route, $params);
} else { } else {
return Yii::$app->getUrlManager()->createUrl($route, $params); return Yii::$app->getUrlManager()->createUrl($route, $params);

5
framework/yii/helpers/SecurityBase.php

@ -120,7 +120,10 @@ class SecurityBase
static $keys; static $keys;
$keyFile = Yii::$app->getRuntimePath() . '/keys.php'; $keyFile = Yii::$app->getRuntimePath() . '/keys.php';
if ($keys === null) { if ($keys === null) {
$keys = is_file($keyFile) ? require($keyFile) : array(); $keys = array();
if (is_file($keyFile)) {
$keys = require($keyFile);
}
} }
if (!isset($keys[$name])) { if (!isset($keys[$name])) {
$keys[$name] = static::generateRandomKey($length); $keys[$name] = static::generateRandomKey($length);

2
framework/yii/requirements/requirements.php

@ -41,7 +41,7 @@ return array(
array( array(
'name' => 'Intl extension', 'name' => 'Intl extension',
'mandatory' => false, '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', '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.' '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.'
), ),

2
framework/yii/validators/Validator.php

@ -49,7 +49,7 @@ abstract class Validator extends Component
*/ */
public static $builtInValidators = array( public static $builtInValidators = array(
'boolean' => 'yii\validators\BooleanValidator', 'boolean' => 'yii\validators\BooleanValidator',
'captcha' => 'yii\validators\CaptchaValidator', 'captcha' => 'yii\captcha\CaptchaValidator',
'compare' => 'yii\validators\CompareValidator', 'compare' => 'yii\validators\CompareValidator',
'date' => 'yii\validators\DateValidator', 'date' => 'yii\validators\DateValidator',
'default' => 'yii\validators\DefaultValueValidator', 'default' => 'yii\validators\DefaultValueValidator',

10
framework/yii/views/errorHandler/callStackItem.php

@ -8,19 +8,19 @@
* @var string[] $lines * @var string[] $lines
* @var integer $begin * @var integer $begin
* @var integer $end * @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); ?>"> data-line="<?php echo (int)($line - $begin); ?>">
<div class="element-wrap"> <div class="element-wrap">
<div class="element"> <div class="element">
<span class="item-number"><?php echo (int)$index; ?>.</span> <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): ?> <?php if ($method !== null): ?>
<span class="call"> <span class="call">
<?php if ($file !== null) echo '&ndash;' ?> <?php if ($file !== null) echo '&ndash;' ?>
<?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> </span>
<?php endif; ?> <?php endif; ?>
<span class="at"><?php if ($line !== null) echo 'at line'; ?></span> <span class="at"><?php if ($line !== null) echo 'at line'; ?></span>
@ -36,7 +36,7 @@
<pre><?php <pre><?php
// fill empty lines with a whitespace to avoid rendering problems in opera // fill empty lines with a whitespace to avoid rendering problems in opera
for ($i = $begin; $i <= $end; ++$i) { 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> ?></pre>
</div> </div>

23
framework/yii/views/errorHandler/error.php

@ -1,10 +1,11 @@
<?php <?php
/** /**
* @var \Exception $exception * @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> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -50,16 +51,18 @@ $title = $this->htmlEncode($exception instanceof \yii\base\Exception ? $exceptio
</head> </head>
<body> <body>
<h1><?php echo $title?></h1> <h1><?php echo $title?></h1>
<h2><?php echo nl2br($this->htmlEncode($exception->getMessage()))?></h2> <h2><?php echo nl2br($handler->htmlEncode($exception->getMessage()))?></h2>
<p> <p>
The above error occurred while the Web server was processing your request. The above error occurred while the Web server was processing your request.
</p> </p>
<p> <p>
Please contact us if you think this is a server error. Thank you. Please contact us if you think this is a server error. Thank you.
</p> </p>
<div class="version"> <div class="version">
<?php echo date('Y-m-d H:i:s', time())?> <?php echo date('Y-m-d H:i:s', time())?>
</div> </div>
<?php if (method_exists($this, 'endBody')) $this->endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?>
</body> </body>
</html> </html>
<?php if (method_exists($this, 'endPage')) $this->endPage(); ?>

47
framework/yii/views/errorHandler/exception.php

File diff suppressed because one or more lines are too long

12
framework/yii/views/errorHandler/previousException.php

@ -1,7 +1,7 @@
<?php <?php
/** /**
* @var \yii\base\Exception $exception * @var \yii\base\Exception $exception
* @var \yii\base\ErrorHandler $this * @var \yii\base\ErrorHandler $handler
*/ */
?> ?>
<div class="previous"> <div class="previous">
@ -9,13 +9,13 @@
<h2> <h2>
<span>Caused by:</span> <span>Caused by:</span>
<?php if ($exception instanceof \yii\base\Exception): ?> <?php if ($exception instanceof \yii\base\Exception): ?>
<span><?php echo $this->htmlEncode($exception->getName()); ?></span> &ndash; <span><?php echo $handler->htmlEncode($exception->getName()); ?></span> &ndash;
<?php echo $this->addTypeLinks(get_class($exception)); ?> <?php echo $handler->addTypeLinks(get_class($exception)); ?>
<?php else: ?> <?php else: ?>
<span><?php echo $this->htmlEncode(get_class($exception)); ?></span> <span><?php echo $handler->htmlEncode(get_class($exception)); ?></span>
<?php endif; ?> <?php endif; ?>
</h2> </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> <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> </div>

2
framework/yii/web/Request.php

@ -864,7 +864,7 @@ class Request extends \yii\base\Request
private $_cookieValidationKey; 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. * a random key will be generated and used.
*/ */
public function getCookieValidationKey() public function getCookieValidationKey()

1
framework/yii/web/Response.php

@ -258,7 +258,6 @@ class Response extends \yii\base\Response
$this->sendHeaders(); $this->sendHeaders();
$this->sendContent(); $this->sendContent();
$this->trigger(self::EVENT_AFTER_SEND, new ResponseEvent($this)); $this->trigger(self::EVENT_AFTER_SEND, new ResponseEvent($this));
$this->clear();
} }
/** /**

6
framework/yii/widgets/DetailView.php

@ -21,7 +21,7 @@ use yii\helpers\Inflector;
* DetailView displays the detail of a single data [[model]]. * 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 * 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. * or an associative array.
* *
* DetailView uses the [[attributes]] property to determines which model attributes * DetailView uses the [[attributes]] property to determines which model attributes
@ -105,7 +105,7 @@ class DetailView extends Widget
public function init() public function init()
{ {
if ($this->model === null) { if ($this->model === null) {
throw new InvalidConfigException('Please specify the "data" property.'); throw new InvalidConfigException('Please specify the "model" property.');
} }
if ($this->formatter == null) { if ($this->formatter == null) {
$this->formatter = Yii::$app->getFormatter(); $this->formatter = Yii::$app->getFormatter();
@ -166,7 +166,7 @@ class DetailView extends Widget
} elseif (is_array($this->model)) { } elseif (is_array($this->model)) {
$this->attributes = array_keys($this->model); $this->attributes = array_keys($this->model);
} else { } 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); sort($this->attributes);
} }

328
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 = '&nbsp;';
/**
* @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.');
}
}
}

28
framework/yii/widgets/ListView.php

@ -19,7 +19,7 @@ use yii\helpers\Html;
class ListView extends ListViewBase 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". * 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. * 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 * for rendering each data item. If it specifies a view name, the following variables will
* be available in the view: * 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 * - `$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]]. * - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]].
* - `$widget`: ListView, this widget instance * - `$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: * 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; public $itemView;
@ -50,40 +50,40 @@ class ListView extends ListViewBase
/** /**
* Renders all data items. * Renders all data models.
* @return string the rendering result * @return string the rendering result
*/ */
public function renderItems() public function renderItems()
{ {
$items = $this->dataProvider->getItems(); $models = $this->dataProvider->getModels();
$keys = $this->dataProvider->getKeys(); $keys = $this->dataProvider->getKeys();
$rows = array(); $rows = array();
foreach (array_values($items) as $index => $item) { foreach (array_values($models) as $index => $model) {
$rows[] = $this->renderItem($item, $keys[$index], $index); $rows[] = $this->renderItem($model, $keys[$index], $index);
} }
return implode($this->separator, $rows); return implode($this->separator, $rows);
} }
/** /**
* Renders a single data item. * Renders a single data model.
* @param mixed $item the data item to be rendered * @param mixed $model the data model to be rendered
* @param mixed $key the key value associated with the data item * @param mixed $key the key value associated with the data model
* @param integer $index the zero-based index of the data item in the item array returned by [[dataProvider]]. * @param integer $index the zero-based index of the data model in the model array returned by [[dataProvider]].
* @return string the rendering result * @return string the rendering result
*/ */
public function renderItem($item, $key, $index) public function renderItem($model, $key, $index)
{ {
if ($this->itemView === null) { if ($this->itemView === null) {
$content = $key; $content = $key;
} elseif (is_string($this->itemView)) { } elseif (is_string($this->itemView)) {
$content = $this->getView()->render($this->itemView, array( $content = $this->getView()->render($this->itemView, array(
'item' => $item, 'model' => $model,
'key' => $key, 'key' => $key,
'index' => $index, 'index' => $index,
'widget' => $this, 'widget' => $this,
)); ));
} else { } else {
$content = call_user_func($this->itemView, $item, $key, $index, $this); $content = call_user_func($this->itemView, $model, $key, $index, $this);
} }
$options = $this->itemOptions; $options = $this->itemOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div'); $tag = ArrayHelper::remove($options, 'tag', 'div');

9
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. * @return string the rendering result.
*/ */
abstract public function renderItems(); abstract public function renderItems();
@ -91,8 +91,9 @@ abstract class ListViewBase extends Widget
public function run() public function run()
{ {
if ($this->dataProvider->getCount() > 0 || $this->empty === false) { if ($this->dataProvider->getCount() > 0 || $this->empty === false) {
$content = preg_replace_callback("/{\\w+}/", function ($matches) { $widget = $this;
$content = $this->renderSection($matches[0]); $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) {
$content = $widget->renderSection($matches[0]);
return $content === false ? $matches[0] : $content; return $content === false ? $matches[0] : $content;
}, $this->layout); }, $this->layout);
} else { } else {
@ -108,7 +109,7 @@ abstract class ListViewBase extends Widget
* @param string $name the section name, e.g., `{summary}`, `{items}`. * @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. * @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) { switch ($name) {
case '{summary}': case '{summary}':

191
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);
}
}

147
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;
}
}

94
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);
}
}

32
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;
}
}
}

2
tests/unit/data/base/Singer.php

@ -15,7 +15,7 @@ class Singer extends Model
return array( return array(
array('lastName', 'default', 'value' => 'Lennon'), array('lastName', 'default', 'value' => 'Lennon'),
array('lastName', 'required'), array('lastName', 'required'),
array('underscore_style', 'yii\validators\CaptchaValidator'), array('underscore_style', 'yii\captcha\CaptchaValidator'),
); );
} }
} }

10
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("123123,12", $this->formatter->asNumber($value, 2));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asNumber(null)); $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'));
}
} }

23
tests/unit/framework/data/ActiveDataProviderTest.php

@ -30,7 +30,7 @@ class ActiveDataProviderTest extends DatabaseTestCase
$provider = new ActiveDataProvider(array( $provider = new ActiveDataProvider(array(
'query' => Order::find()->orderBy('id'), 'query' => Order::find()->orderBy('id'),
)); ));
$orders = $provider->getItems(); $orders = $provider->getModels();
$this->assertEquals(3, count($orders)); $this->assertEquals(3, count($orders));
$this->assertTrue($orders[0] instanceof Order); $this->assertTrue($orders[0] instanceof Order);
$this->assertEquals(array(1, 2, 3), $provider->getKeys()); $this->assertEquals(array(1, 2, 3), $provider->getKeys());
@ -41,7 +41,7 @@ class ActiveDataProviderTest extends DatabaseTestCase
'pageSize' => 2, 'pageSize' => 2,
) )
)); ));
$orders = $provider->getItems(); $orders = $provider->getModels();
$this->assertEquals(2, count($orders)); $this->assertEquals(2, count($orders));
} }
@ -52,7 +52,7 @@ class ActiveDataProviderTest extends DatabaseTestCase
'db' => $this->getConnection(), 'db' => $this->getConnection(),
'query' => $query->from('tbl_order')->orderBy('id'), 'query' => $query->from('tbl_order')->orderBy('id'),
)); ));
$orders = $provider->getItems(); $orders = $provider->getModels();
$this->assertEquals(3, count($orders)); $this->assertEquals(3, count($orders));
$this->assertTrue(is_array($orders[0])); $this->assertTrue(is_array($orders[0]));
$this->assertEquals(array(0, 1, 2), $provider->getKeys()); $this->assertEquals(array(0, 1, 2), $provider->getKeys());
@ -65,7 +65,22 @@ class ActiveDataProviderTest extends DatabaseTestCase
'pageSize' => 2, 'pageSize' => 2,
) )
)); ));
$orders = $provider->getItems(); $orders = $provider->getModels();
$this->assertEquals(2, count($orders)); $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()));
}
} }

Loading…
Cancel
Save