diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e4b8278
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: php
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+
+env:
+ - DB=mysql
+
+before_script:
+ - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi"
+
+script: phpunit
\ No newline at end of file
diff --git a/app/protected/views/layouts/main.php b/app/protected/views/layouts/main.php
deleted file mode 100644
index 89c141f..0000000
--- a/app/protected/views/layouts/main.php
+++ /dev/null
@@ -1,26 +0,0 @@
-
-beginPage(); ?>
-
-
-
-
- title); ?>
- 'screen')); ?>
- head(); ?>
-
-
-
-
Welcome
- beginBody(); ?>
-
- endBody(); ?>
-
-
-
-endPage(); ?>
\ No newline at end of file
diff --git a/app/protected/views/site/index.php b/app/protected/views/site/index.php
deleted file mode 100644
index 66e4dd1..0000000
--- a/app/protected/views/site/index.php
+++ /dev/null
@@ -1,17 +0,0 @@
-title = 'Hello World';
-
-$user = Yii::$app->getUser();
-if ($user->isGuest) {
- echo Html::a('login', array('login'));
-} else {
- echo "You are logged in as " . $user->identity->username . "
";
- echo Html::a('logout', array('logout'));
-}
-?>
-
-
diff --git a/app/assets/.gitignore b/apps/bootstrap/assets/.gitignore
similarity index 100%
rename from app/assets/.gitignore
rename to apps/bootstrap/assets/.gitignore
diff --git a/app/css/bootstrap-responsive.css b/apps/bootstrap/css/bootstrap-responsive.css
similarity index 100%
rename from app/css/bootstrap-responsive.css
rename to apps/bootstrap/css/bootstrap-responsive.css
diff --git a/app/css/bootstrap-responsive.min.css b/apps/bootstrap/css/bootstrap-responsive.min.css
similarity index 100%
rename from app/css/bootstrap-responsive.min.css
rename to apps/bootstrap/css/bootstrap-responsive.min.css
diff --git a/app/css/bootstrap.css b/apps/bootstrap/css/bootstrap.css
similarity index 100%
rename from app/css/bootstrap.css
rename to apps/bootstrap/css/bootstrap.css
diff --git a/app/css/bootstrap.min.css b/apps/bootstrap/css/bootstrap.min.css
similarity index 100%
rename from app/css/bootstrap.min.css
rename to apps/bootstrap/css/bootstrap.min.css
diff --git a/apps/bootstrap/css/site.css b/apps/bootstrap/css/site.css
new file mode 100644
index 0000000..890a953
--- /dev/null
+++ b/apps/bootstrap/css/site.css
@@ -0,0 +1,78 @@
+body {
+ padding-top: 20px;
+ padding-bottom: 60px;
+}
+
+/* Custom container */
+.container {
+ margin: 0 auto;
+ max-width: 1000px;
+}
+
+.container > hr {
+ margin: 60px 0;
+}
+
+/* Main marketing message and sign up button */
+.jumbotron {
+ margin: 80px 0;
+ text-align: center;
+}
+
+.jumbotron h1 {
+ font-size: 100px;
+ line-height: 1;
+}
+
+.jumbotron .lead {
+ font-size: 24px;
+ line-height: 1.25;
+}
+
+.jumbotron .btn {
+ font-size: 21px;
+ padding: 14px 24px;
+}
+
+/* Supporting marketing content */
+.marketing {
+ margin: 60px 0;
+}
+
+.marketing p + h4 {
+ margin-top: 28px;
+}
+
+/* Customize the navbar links to be fill the entire space of the .navbar */
+.navbar .navbar-inner {
+ padding: 0;
+}
+
+.navbar .nav {
+ margin: 0;
+ display: table;
+ width: 100%;
+}
+
+.navbar .nav li {
+ display: table-cell;
+ width: 1%;
+ float: none;
+}
+
+.navbar .nav li a {
+ font-weight: bold;
+ text-align: center;
+ border-left: 1px solid rgba(255, 255, 255, .75);
+ border-right: 1px solid rgba(0, 0, 0, .1);
+}
+
+.navbar .nav li:first-child a {
+ border-left: 0;
+ border-radius: 3px 0 0 3px;
+}
+
+.navbar .nav li:last-child a {
+ border-right: 0;
+ border-radius: 0 3px 3px 0;
+}
diff --git a/app/img/glyphicons-halflings-white.png b/apps/bootstrap/img/glyphicons-halflings-white.png
similarity index 100%
rename from app/img/glyphicons-halflings-white.png
rename to apps/bootstrap/img/glyphicons-halflings-white.png
diff --git a/app/img/glyphicons-halflings.png b/apps/bootstrap/img/glyphicons-halflings.png
similarity index 100%
rename from app/img/glyphicons-halflings.png
rename to apps/bootstrap/img/glyphicons-halflings.png
diff --git a/app/index.php b/apps/bootstrap/index.php
similarity index 80%
rename from app/index.php
rename to apps/bootstrap/index.php
index 8f98090..e3188c1 100644
--- a/app/index.php
+++ b/apps/bootstrap/index.php
@@ -2,7 +2,7 @@
defined('YII_DEBUG') or define('YII_DEBUG', true);
-require(__DIR__ . '/../framework/yii.php');
+require(__DIR__ . '/../../framework/yii.php');
$config = require(__DIR__ . '/protected/config/main.php');
$application = new yii\web\Application($config);
diff --git a/app/js/bootstrap.js b/apps/bootstrap/js/bootstrap.js
similarity index 100%
rename from app/js/bootstrap.js
rename to apps/bootstrap/js/bootstrap.js
diff --git a/app/js/bootstrap.min.js b/apps/bootstrap/js/bootstrap.min.js
similarity index 100%
rename from app/js/bootstrap.min.js
rename to apps/bootstrap/js/bootstrap.min.js
diff --git a/apps/bootstrap/protected/.htaccess b/apps/bootstrap/protected/.htaccess
new file mode 100644
index 0000000..e019832
--- /dev/null
+++ b/apps/bootstrap/protected/.htaccess
@@ -0,0 +1 @@
+deny from all
diff --git a/apps/bootstrap/protected/config/assets.php b/apps/bootstrap/protected/config/assets.php
new file mode 100644
index 0000000..a3ba847
--- /dev/null
+++ b/apps/bootstrap/protected/config/assets.php
@@ -0,0 +1,19 @@
+ array(
+ 'basePath' => '@wwwroot',
+ 'baseUrl' => '@www',
+ 'css' => array(
+ 'css/bootstrap.min.css',
+ 'css/bootstrap-responsive.min.css',
+ 'css/site.css',
+ ),
+ 'js' => array(
+
+ ),
+ 'depends' => array(
+ 'yii',
+ ),
+ ),
+);
diff --git a/app/protected/config/main.php b/apps/bootstrap/protected/config/main.php
similarity index 62%
rename from app/protected/config/main.php
rename to apps/bootstrap/protected/config/main.php
index e18ead8..96e0986 100644
--- a/app/protected/config/main.php
+++ b/apps/bootstrap/protected/config/main.php
@@ -10,6 +10,12 @@ return array(
'user' => array(
'class' => 'yii\web\User',
'identityClass' => 'app\models\User',
- )
+ ),
+ 'assetManager' => array(
+ 'bundles' => require(__DIR__ . '/assets.php'),
+ ),
+ ),
+ 'params' => array(
+ 'adminEmail' => 'admin@example.com',
),
-);
\ No newline at end of file
+);
diff --git a/app/protected/controllers/SiteController.php b/apps/bootstrap/protected/controllers/SiteController.php
similarity index 50%
rename from app/protected/controllers/SiteController.php
rename to apps/bootstrap/protected/controllers/SiteController.php
index 39e9c1f..d1186f6 100644
--- a/app/protected/controllers/SiteController.php
+++ b/apps/bootstrap/protected/controllers/SiteController.php
@@ -2,6 +2,7 @@
use yii\web\Controller;
use app\models\LoginForm;
+use app\models\ContactForm;
class SiteController extends Controller
{
@@ -14,7 +15,7 @@ class SiteController extends Controller
{
$model = new LoginForm();
if ($this->populate($_POST, $model) && $model->login()) {
- Yii::$app->getResponse()->redirect(array('site/index'));
+ Yii::$app->response->redirect(array('site/index'));
} else {
echo $this->render('login', array(
'model' => $model,
@@ -27,4 +28,22 @@ class SiteController extends Controller
Yii::$app->getUser()->logout();
Yii::$app->getResponse()->redirect(array('site/index'));
}
-}
\ No newline at end of file
+
+ public function actionContact()
+ {
+ $model = new ContactForm;
+ if ($this->populate($_POST, $model) && $model->contact(Yii::$app->params['adminEmail'])) {
+ Yii::$app->session->setFlash('contactFormSubmitted');
+ Yii::$app->response->refresh();
+ } else {
+ echo $this->render('contact', array(
+ 'model' => $model,
+ ));
+ }
+ }
+
+ public function actionAbout()
+ {
+ echo $this->render('about');
+ }
+}
diff --git a/apps/bootstrap/protected/models/ContactForm.php b/apps/bootstrap/protected/models/ContactForm.php
new file mode 100644
index 0000000..5124b2c
--- /dev/null
+++ b/apps/bootstrap/protected/models/ContactForm.php
@@ -0,0 +1,63 @@
+ !Captcha::checkRequirements()),
+ );
+ }
+
+ /**
+ * @return array customized attribute labels
+ */
+ public function attributeLabels()
+ {
+ return array(
+ 'verifyCode' => 'Verification Code',
+ );
+ }
+
+ /**
+ * Sends an email to the specified email address using the information collected by this model.
+ * @param string $email the target email address
+ * @return boolean whether the model passes validation
+ */
+ public function contact($email)
+ {
+ if ($this->validate()) {
+ $name = '=?UTF-8?B?' . base64_encode($this->name) . '?=';
+ $subject = '=?UTF-8?B?' . base64_encode($this->subject) . '?=';
+ $headers = "From: $name <{$this->email}>\r\n" .
+ "Reply-To: {$this->email}\r\n" .
+ "MIME-Version: 1.0\r\n" .
+ "Content-type: text/plain; charset=UTF-8";
+ mail($email, $subject, $this->body, $headers);
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/app/protected/models/LoginForm.php b/apps/bootstrap/protected/models/LoginForm.php
similarity index 53%
rename from app/protected/models/LoginForm.php
rename to apps/bootstrap/protected/models/LoginForm.php
index b68e146..5ba1dc6 100644
--- a/app/protected/models/LoginForm.php
+++ b/apps/bootstrap/protected/models/LoginForm.php
@@ -1,9 +1,4 @@
- * @since 2.0
+ * LoginForm is the model behind the login form.
*/
class LoginForm extends Model
{
@@ -20,16 +14,25 @@ class LoginForm extends Model
public $password;
public $rememberMe = true;
+ /**
+ * @return array the validation rules.
+ */
public function rules()
{
return array(
- array('username', 'required'),
- array('password', 'required'),
+ // username and password are both required
+ array('username, password', 'required'),
+ // password is validated by validatePassword()
array('password', 'validatePassword'),
+ // rememberMe must be a boolean value
array('rememberMe', 'boolean'),
);
}
+ /**
+ * Validates the password.
+ * This method serves as the inline validation for password.
+ */
public function validatePassword()
{
$user = User::findByUsername($this->username);
@@ -38,14 +41,18 @@ class LoginForm extends Model
}
}
+ /**
+ * Logs in a user using the provided username and password.
+ * @return boolean whether the user is logged in successfully
+ */
public function login()
{
if ($this->validate()) {
$user = User::findByUsername($this->username);
- Yii::$app->getUser()->login($user, $this->rememberMe ? 3600*24*30 : 0);
+ Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0);
return true;
} else {
return false;
}
}
-}
\ No newline at end of file
+}
diff --git a/app/protected/models/User.php b/apps/bootstrap/protected/models/User.php
similarity index 99%
rename from app/protected/models/User.php
rename to apps/bootstrap/protected/models/User.php
index fcbf14a..afbf9f8 100644
--- a/app/protected/models/User.php
+++ b/apps/bootstrap/protected/models/User.php
@@ -58,4 +58,4 @@ class User extends \yii\base\Object implements \yii\web\Identity
{
return $this->password === $password;
}
-}
\ No newline at end of file
+}
diff --git a/app/protected/runtime/.gitignore b/apps/bootstrap/protected/runtime/.gitignore
similarity index 100%
rename from app/protected/runtime/.gitignore
rename to apps/bootstrap/protected/runtime/.gitignore
diff --git a/apps/bootstrap/protected/views/layouts/main.php b/apps/bootstrap/protected/views/layouts/main.php
new file mode 100644
index 0000000..1240053
--- /dev/null
+++ b/apps/bootstrap/protected/views/layouts/main.php
@@ -0,0 +1,60 @@
+registerAssetBundle('app');
+?>
+beginPage(); ?>
+
+
+
+
+ title); ?>
+ head(); ?>
+
+
+
+ beginBody(); ?>
+
+
+ widget('yii\widgets\Breadcrumbs', array(
+ 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(),
+ )); ?>
+
+
+
+
+
+ endBody(); ?>
+
+
+
+endPage(); ?>
diff --git a/apps/bootstrap/protected/views/site/about.php b/apps/bootstrap/protected/views/site/about.php
new file mode 100644
index 0000000..86e19e1
--- /dev/null
+++ b/apps/bootstrap/protected/views/site/about.php
@@ -0,0 +1,16 @@
+title = 'About';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+title); ?>
+
+
+ This is the About page. You may modify the following file to customize its content:
+
+
+
+
diff --git a/apps/bootstrap/protected/views/site/contact.php b/apps/bootstrap/protected/views/site/contact.php
new file mode 100644
index 0000000..5cb5a8e
--- /dev/null
+++ b/apps/bootstrap/protected/views/site/contact.php
@@ -0,0 +1,34 @@
+title = 'Contact';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+title); ?>
+
+session->hasFlash('contactFormSubmitted')): ?>
+
+ Thank you for contacting us. We will respond to you as soon as possible.
+
+
+
+
+ If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
+
+
+beginWidget('yii\widgets\ActiveForm', array(
+ 'options' => array('class' => 'form-horizontal'),
+ 'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')),
+)); ?>
+ field($model, 'name')->textInput(); ?>
+ field($model, 'email')->textInput(); ?>
+ field($model, 'subject')->textInput(); ?>
+ field($model, 'body')->textArea(array('rows' => 6)); ?>
+
+ 'btn btn-primary')); ?>
+
+endWidget(); ?>
diff --git a/apps/bootstrap/protected/views/site/index.php b/apps/bootstrap/protected/views/site/index.php
new file mode 100644
index 0000000..158b61c
--- /dev/null
+++ b/apps/bootstrap/protected/views/site/index.php
@@ -0,0 +1,47 @@
+title = 'Welcome';
+?>
+
+
Welcome!
+
+
Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus
+ commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
+
Get started with Yii
+
+
+
+
+
+
+
+
Heading
+
+
Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
+ condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
+ Donec sed odio dui.
+
+
View details »
+
+
+
Heading
+
+
Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
+ condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
+ Donec sed odio dui.
+
+
View details »
+
+
+
Heading
+
+
Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta
+ felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum
+ massa.
+
+
View details »
+
+
+
diff --git a/app/protected/views/site/login.php b/apps/bootstrap/protected/views/site/login.php
similarity index 65%
rename from app/protected/views/site/login.php
rename to apps/bootstrap/protected/views/site/login.php
index e6da8ef..f7f842e 100644
--- a/app/protected/views/site/login.php
+++ b/apps/bootstrap/protected/views/site/login.php
@@ -5,8 +5,10 @@ use yii\helpers\Html;
* @var yii\widgets\ActiveForm $form
* @var app\models\LoginForm $model
*/
+$this->title = 'Login';
+$this->params['breadcrumbs'][] = $this->title;
?>
-Login
+title); ?>
Please fill out the following fields to login:
@@ -14,9 +16,7 @@ use yii\helpers\Html;
field($model, 'username')->textInput(); ?>
field($model, 'password')->passwordInput(); ?>
field($model, 'rememberMe')->checkbox(); ?>
-
-
- 'btn btn-primary')); ?>
-
+
+ 'btn btn-primary')); ?>
-endWidget(); ?>
\ No newline at end of file
+endWidget(); ?>
diff --git a/build/build b/build/build
index fff4282..691eba9 100755
--- a/build/build
+++ b/build/build
@@ -16,5 +16,5 @@ require(__DIR__ . '/../framework/yii.php');
$id = 'yiic-build';
$basePath = __DIR__;
-$application = new yii\console\Application($id, $basePath);
+$application = new yii\console\Application(array('id' => $id, 'basePath' => $basePath));
$application->run();
diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md
index 822c548..4e82793 100644
--- a/docs/api/db/ActiveRecord.md
+++ b/docs/api/db/ActiveRecord.md
@@ -412,7 +412,7 @@ class Customer extends \yii\db\ActiveRecord
/**
* @param ActiveQuery $query
*/
- public function active($query)
+ public static function active($query)
{
$query->andWhere('status = 1');
}
@@ -435,7 +435,7 @@ class Customer extends \yii\db\ActiveRecord
* @param ActiveQuery $query
* @param integer $age
*/
- public function olderThan($query, $age = 30)
+ public static function olderThan($query, $age = 30)
{
$query->andWhere('age > :age', array(':age' => $age));
}
diff --git a/docs/guide/active-record.md b/docs/guide/active-record.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/application.md b/docs/guide/application.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/authentication.md b/docs/guide/authentication.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/authorization.md b/docs/guide/authorization.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/bootstrap.md b/docs/guide/bootstrap.md
new file mode 100644
index 0000000..1bc3fe6
--- /dev/null
+++ b/docs/guide/bootstrap.md
@@ -0,0 +1,63 @@
+Bootstrap with Yii
+==================
+
+A ready-to-use Web application is distributed together with Yii. You may find
+its source code under the `app` folder after you expand the Yii release file.
+If you have installed Yii under a Web-accessible folder, you should be able to
+access this application through the following URL:
+
+~~~
+http://localhost/yii/apps/bootstrap/index.php
+~~~
+
+
+As you can see, the application has four pages: the homepage, the about page,
+the contact page and the login page. The contact page displays a contact
+form that users can fill in to submit their inquiries to the webmaster,
+and the login page allows users to be authenticated before accessing privileged contents.
+
+
+The following diagram shows the directory structure of this application.
+
+~~~
+app/
+ index.php Web application entry script file
+ index-test.php entry script file for the functional tests
+ assets/ containing published resource files
+ css/ containing CSS files
+ img/ containing image files
+ themes/ containing application themes
+ protected/ containing protected application files
+ yiic yiic command line script for Unix/Linux
+ yiic.bat yiic command line script for Windows
+ yiic.php yiic command line PHP script
+ commands/ containing customized 'yiic' commands
+ components/ containing reusable user components
+ config/ containing configuration files
+ console.php the console application configuration
+ main.php the Web application configuration
+ controllers/ containing controller class files
+ SiteController.php the default controller class
+ data/ containing the sample database
+ schema.mysql.sql the DB schema for the sample MySQL database
+ schema.sqlite.sql the DB schema for the sample SQLite database
+ bootstrap.db the sample SQLite database file
+ vendor/ containing third-party extensions and libraries
+ messages/ containing translated messages
+ models/ containing model class files
+ User.php the User model
+ LoginForm.php the form model for 'login' action
+ ContactForm.php the form model for 'contact' action
+ runtime/ containing temporarily generated files
+ views/ containing controller view and layout files
+ layouts/ containing layout view files
+ main.php the base layout shared by all pages
+ site/ containing view files for the 'site' controller
+ about.php the view for the 'about' action
+ contact.php the view for the 'contact' action
+ index.php the view for the 'index' action
+ login.php the view for the 'login' action
+~~~
+
+
+TBD
\ No newline at end of file
diff --git a/docs/guide/caching.md b/docs/guide/caching.md
new file mode 100644
index 0000000..cd945e7
--- /dev/null
+++ b/docs/guide/caching.md
@@ -0,0 +1,3 @@
+Caching
+=======
+
diff --git a/docs/guide/console.md b/docs/guide/console.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/controller.md b/docs/guide/controller.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/dao.md b/docs/guide/dao.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/error.md b/docs/guide/error.md
new file mode 100644
index 0000000..c97fada
--- /dev/null
+++ b/docs/guide/error.md
@@ -0,0 +1,3 @@
+Error Handling
+==============
+
diff --git a/docs/guide/extension.md b/docs/guide/extension.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/form.md b/docs/guide/form.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/gii.md b/docs/guide/gii.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/index.md b/docs/guide/index.md
new file mode 100644
index 0000000..dd72ca3
--- /dev/null
+++ b/docs/guide/index.md
@@ -0,0 +1,30 @@
+* [Overview](overview.md)
+* [Installation](installation.md)
+* [Bootstrap with Yii](bootstrap.md)
+* [MVC Overview](mvc.md)
+* [Controller](controller.md)
+* [Model](model.md)
+* [View](view.md)
+* [Application](application.md)
+* [Form](form.md)
+* [Data Validation](validation.md)
+* [Database Access Objects](dao.md)
+* [Query Builder](query-builder.md)
+* [ActiveRecord](active-record.md)
+* [Database Migration](migration.md)
+* [Caching](caching.md)
+* [Internationalization](i18n.md)
+* [Extending Yii](extension.md)
+* [Authentication](authentication.md)
+* [Authorization](authorization.md)
+* [Logging](logging.md)
+* [URL Management](url.md)
+* [Theming](theming.md)
+* [Error Handling](error.md)
+* [Template](template.md)
+* [Console Application](console.md)
+* [Security](security.md)
+* [Performance Tuning](performance.md)
+* [Testing](testing.md)
+* [Automatic Code Generation](gii.md)
+* [Upgrading from 1.1 to 2.0](upgrade-from-v1.md)
diff --git a/docs/guide/installation.md b/docs/guide/installation.md
new file mode 100644
index 0000000..3f9a803
--- /dev/null
+++ b/docs/guide/installation.md
@@ -0,0 +1,112 @@
+Installation
+============
+
+Installation of Yii mainly involves the following two steps:
+
+ 1. Download Yii Framework from [yiiframework.com](http://www.yiiframework.com/).
+ 2. Unpack the Yii release file to a Web-accessible directory.
+
+> Tip: Yii does not need to be installed under a Web-accessible directory.
+A Yii application has one entry script which is usually the only file that
+needs to be exposed to Web users. Other PHP scripts, including those from
+Yii, should be protected from Web access; otherwise they might be exploited
+by hackers.
+
+
+Requirements
+------------
+
+After installing Yii, you may want to verify that your server satisfies
+Yii's requirements. You can do so by accessing the requirement checker
+script via the following URL in a Web browser:
+
+~~~
+http://hostname/path/to/yii/requirements/index.php
+~~~
+
+Yii requires PHP 5.3, so the server must have PHP 5.3 or above installed and
+available to the web server. Yii has been tested with [Apache HTTP server](http://httpd.apache.org/)
+on Windows and Linux. It may also run on other Web servers and platforms,
+provided PHP 5.3 is supported.
+
+
+Recommended Apache Configuration
+--------------------------------
+
+Yii is ready to work with a default Apache web server configuration.
+The `.htaccess` files in Yii framework and application folders deny
+access to the restricted resources. To hide the bootstrap file (usually `index.php`)
+in your URLs you can add `mod_rewrite` instructions to the `.htaccess` file
+in your document root or to the virtual host configuration:
+
+~~~
+RewriteEngine on
+
+# if a directory or a file exists, use it directly
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+# otherwise forward it to index.php
+RewriteRule . index.php
+~~~
+
+
+Recommended Nginx Configuration
+-------------------------------
+
+You can use Yii with [Nginx](http://wiki.nginx.org/) and PHP with [FPM SAPI](http://php.net/install.fpm).
+Here is a sample host configuration. It defines the bootstrap file and makes
+Yii to catch all requests to nonexistent files, which allows us to have nice-looking URLs.
+
+~~~
+server {
+ set $host_path "/www/mysite";
+ access_log /www/mysite/log/access.log main;
+
+ server_name mysite;
+ root $host_path/htdocs;
+ set $yii_bootstrap "index.php";
+
+ charset utf-8;
+
+ location / {
+ index index.html $yii_bootstrap;
+ try_files $uri $uri/ /$yii_bootstrap?$args;
+ }
+
+ location ~ ^/(protected|framework|themes/\w+/views) {
+ deny all;
+ }
+
+ #avoid processing of calls to unexisting static files by yii
+ location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
+ try_files $uri =404;
+ }
+
+ # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+ #
+ location ~ \.php {
+ fastcgi_split_path_info ^(.+\.php)(.*)$;
+
+ #let yii catch the calls to unexising PHP files
+ set $fsn /$yii_bootstrap;
+ if (-f $document_root$fastcgi_script_name){
+ set $fsn $fastcgi_script_name;
+ }
+
+ fastcgi_pass 127.0.0.1:9000;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fsn;
+
+ #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ fastcgi_param PATH_TRANSLATED $document_root$fsn;
+ }
+
+ location ~ /\.ht {
+ deny all;
+ }
+}
+~~~
+
+Using this configuration you can set `cgi.fix_pathinfo=0` in php.ini to avoid
+many unnecessary system `stat()` calls.
diff --git a/docs/guide/logging.md b/docs/guide/logging.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/migration.md b/docs/guide/migration.md
new file mode 100644
index 0000000..bafd293
--- /dev/null
+++ b/docs/guide/migration.md
@@ -0,0 +1,319 @@
+Database Migration
+==================
+
+Like source code, the structure of a database is evolving as we develop and maintain
+a database-driven application. For example, during development, we may want to
+add a new table; or after the application is put into production, we may realize
+the need of adding an index on a column. It is important to keep track of these
+structural database changes (called **migration**) like we do with our source
+code. If the source code and the database are out of sync, it is very likely
+the whole system may break. For this reason, Yii provides a database migration
+tool that can keep track of database migration history, apply new migrations,
+or revert existing ones.
+
+The following steps show how we can use database migration during development:
+
+1. Tim creates a new migration (e.g. create a new table)
+2. Tim commits the new migration into source control system (e.g. GIT, Mercurial)
+3. Doug updates from source control system and receives the new migration
+4. Doug applies the migration to his local development database
+
+
+Yii supports database migration via the `yiic migrate` command line tool. This
+tool supports creating new migrations, applying/reverting/redoing migrations, and
+showing migration history and new migrations.
+
+Creating Migrations
+-------------------
+
+To create a new migration (e.g. create a news table), we run the following command:
+
+~~~
+yiic migrate/create
+~~~
+
+The required `name` parameter specifies a very brief description of the migration
+(e.g. `create_news_table`). As we will show in the following, the `name` parameter
+is used as part of a PHP class name. Therefore, it should only contain letters,
+digits and/or underscore characters.
+
+~~~
+yiic migrate/create create_news_table
+~~~
+
+The above command will create under the `protected/migrations` directory a new
+file named `m101129_185401_create_news_table.php` which contains the following
+initial code:
+
+~~~
+[php]
+class m101129_185401_create_news_table extends \yii\db\Migration
+{
+ public function up()
+ {
+ }
+
+ public function down()
+ {
+ echo "m101129_185401_create_news_table cannot be reverted.\n";
+ return false;
+ }
+}
+~~~
+
+Notice that the class name is the same as the file name which is of the pattern
+`m_`, where `` refers to the UTC timestamp (in the
+format of `yymmdd_hhmmss`) when the migration is created, and `` is taken
+from the command's `name` parameter.
+
+The `up()` method should contain the code implementing the actual database
+migration, while the `down()` method may contain the code reverting what is
+done in `up()`.
+
+Sometimes, it is impossible to implement `down()`. For example, if we delete
+table rows in `up()`, we will not be able to recover them in `down()`. In this
+case, the migration is called irreversible, meaning we cannot roll back to
+a previous state of the database. In the above generated code, the `down()`
+method returns `false` to indicate that the migration cannot be reverted.
+
+As an example, let's show the migration about creating a news table.
+
+~~~
+[php]
+class m101129_185401_create_news_table extends \yii\db\Migration
+{
+ public function up()
+ {
+ $this->db->createCommand()->createTable('tbl_news, array(
+ 'id' => 'pk',
+ 'title' => 'string NOT NULL',
+ 'content' => 'text',
+ ))->execute();
+ }
+
+ public function down()
+ {
+ $this->db->createCommand()->dropTable('tbl_news')->execute();
+ }
+}
+~~~
+
+The base class [\yii\db\Migration] exposes a database connection via `db`
+property. You can use it for manipulating data and schema of a database.
+
+Transactional Migrations
+------------------------
+
+While performing complex DB migrations, we usually want to make sure that each
+migration succeed or fail as a whole so that the database maintains the
+consistency and integrity. In order to achieve this goal, we can exploit
+DB transactions.
+
+We could explicitly start a DB transaction and enclose the rest of the DB-related
+code within the transaction, like the following:
+
+~~~
+[php]
+class m101129_185401_create_news_table extends \yii\db\Migration
+{
+ public function up()
+ {
+ $transaction=$this->getDbConnection()->beginTransaction();
+ try
+ {
+ $this->db->createCommand()->createTable('tbl_news, array(
+ 'id' => 'pk',
+ 'title' => 'string NOT NULL',
+ 'content' => 'text',
+ ))->execute();
+ $transaction->commit();
+ }
+ catch(Exception $e)
+ {
+ echo "Exception: ".$e->getMessage()."\n";
+ $transaction->rollback();
+ return false;
+ }
+ }
+
+ // ...similar code for down()
+}
+~~~
+
+> Note: Not all DBMS support transactions. And some DB queries cannot be put
+> into a transaction. In this case, you will have to implement `up()` and
+> `down()`, instead. And for MySQL, some SQL statements may cause
+> [implicit commit](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html).
+
+
+Applying Migrations
+-------------------
+
+To apply all available new migrations (i.e., make the local database up-to-date),
+run the following command:
+
+~~~
+yiic migrate
+~~~
+
+The command will show the list of all new migrations. If you confirm to apply
+the migrations, it will run the `up()` method in every new migration class, one
+after another, in the order of the timestamp value in the class name.
+
+After applying a migration, the migration tool will keep a record in a database
+table named `tbl_migration`. This allows the tool to identify which migrations
+have been applied and which are not. If the `tbl_migration` table does not exist,
+the tool will automatically create it in the database specified by the `db`
+application component.
+
+Sometimes, we may only want to apply one or a few new migrations. We can use the
+following command:
+
+~~~
+yiic migrate/up 3
+~~~
+
+This command will apply the 3 new migrations. Changing the value 3 will allow
+us to change the number of migrations to be applied.
+
+We can also migrate the database to a specific version with the following command:
+
+~~~
+yiic migrate/to 101129_185401
+~~~
+
+That is, we use the timestamp part of a migration name to specify the version
+that we want to migrate the database to. If there are multiple migrations between
+the last applied migration and the specified migration, all these migrations
+will be applied. If the specified migration has been applied before, then all
+migrations applied after it will be reverted (to be described in the next section).
+
+
+Reverting Migrations
+--------------------
+
+To revert the last one or several applied migrations, we can use the following
+command:
+
+~~~
+yiic migrate/down [step]
+~~~
+
+where the optional `step` parameter specifies how many migrations to be reverted
+back. It defaults to 1, meaning reverting back the last applied migration.
+
+As we described before, not all migrations can be reverted. Trying to revert
+such migrations will throw an exception and stop the whole reverting process.
+
+
+Redoing Migrations
+------------------
+
+Redoing migrations means first reverting and then applying the specified migrations.
+This can be done with the following command:
+
+~~~
+yiic migrate/redo [step]
+~~~
+
+where the optional `step` parameter specifies how many migrations to be redone.
+It defaults to 1, meaning redoing the last migration.
+
+
+Showing Migration Information
+-----------------------------
+
+Besides applying and reverting migrations, the migration tool can also display
+the migration history and the new migrations to be applied.
+
+~~~
+yiic migrate/history [limit]
+yiic migrate/new [limit]
+~~~
+
+where the optional parameter `limit` specifies the number of migrations to be
+displayed. If `limit` is not specified, all available migrations will be displayed.
+
+The first command shows the migrations that have been applied, while the second
+command shows the migrations that have not been applied.
+
+
+Modifying Migration History
+---------------------------
+
+Sometimes, we may want to modify the migration history to a specific migration
+version without actually applying or reverting the relevant migrations. This
+often happens when developing a new migration. We can use the following command
+to achieve this goal.
+
+~~~
+yiic migrate/mark 101129_185401
+~~~
+
+This command is very similar to `yiic migrate/to` command, except that it only
+modifies the migration history table to the specified version without applying
+or reverting the migrations.
+
+
+Customizing Migration Command
+-----------------------------
+
+There are several ways to customize the migration command.
+
+### Use Command Line Options
+
+The migration command comes with four options that can be specified in command
+line:
+
+* `interactive`: boolean, specifies whether to perform migrations in an
+ interactive mode. Defaults to true, meaning the user will be prompted when
+ performing a specific migration. You may set this to false should the
+ migrations be done in a background process.
+
+* `migrationPath`: string, specifies the directory storing all migration class
+ files. This must be specified in terms of a path alias, and the corresponding
+ directory must exist. If not specified, it will use the `migrations`
+ sub-directory under the application base path.
+
+* `migrationTable`: string, specifies the name of the database table for storing
+ migration history information. It defaults to `tbl_migration`. The table
+ structure is `version varchar(255) primary key, apply_time integer`.
+
+* `connectionID`: string, specifies the ID of the database application component.
+ Defaults to 'db'.
+
+* `templateFile`: string, specifies the path of the file to be served as the code
+ template for generating the migration classes. This must be specified in terms
+ of a path alias (e.g. `application.migrations.template`). If not set, an
+ internal template will be used. Inside the template, the token `{ClassName}`
+ will be replaced with the actual migration class name.
+
+To specify these options, execute the migrate command using the following format
+
+~~~
+yiic migrate/up --option1=value1 --option2=value2 ...
+~~~
+
+For example, if we want to migrate for a `forum` module whose migration files
+are located within the module's `migrations` directory, we can use the following
+command:
+
+~~~
+yiic migrate/up --migrationPath=ext.forum.migrations
+~~~
+
+
+### Configure Command Globally
+
+While command line options allow us to configure the migration command
+on-the-fly, sometimes we may want to configure the command once for all.
+For example, we may want to use a different table to store the migration history,
+or we may want to use a customized migration template. We can do so by modifying
+the console application's configuration file like the following,
+
+```php
+TBD
+```
+
+Now if we run the `migrate` command, the above configurations will take effect
+without requiring us to enter the command line options every time.
diff --git a/docs/guide/model.md b/docs/guide/model.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/mvc.md b/docs/guide/mvc.md
new file mode 100644
index 0000000..a99d043
--- /dev/null
+++ b/docs/guide/mvc.md
@@ -0,0 +1,52 @@
+MVC Overview
+============
+
+Yii implements the model-view-controller (MVC) design pattern, which is
+widely adopted in Web programming. MVC aims to separate business logic from
+user interface considerations, so that developers can more easily change
+each part without affecting the other. In MVC, the model represents the
+information (the data) and the business rules; the view contains elements
+of the user interface such as text, form inputs; and the controller manages
+the communication between the model and the view.
+
+Besides implementing MVC, Yii also introduces a front-controller, called
+`Application`, which encapsulates the execution context for the processing
+of a request. Application collects information about a user request and
+then dispatches it to an appropriate controller for further handling.
+
+The following diagram shows the static structure of a Yii application:
+
+![Static structure of Yii application](structure.png)
+
+
+A Typical Workflow
+------------------
+
+The following diagram shows a typical workflow of a Yii application when
+it is handling a user request:
+
+![Typical workflow of a Yii application](flow.png)
+
+ 1. A user makes a request with the URL `http://www.example.com/index.php?r=post/show&id=1`
+and the Web server handles the request by executing the bootstrap script `index.php`.
+ 2. The bootstrap script creates an [Application](/doc/guide/basics.application)
+instance and runs it.
+ 3. The Application obtains detailed user request information from
+an [application component](/doc/guide/basics.application#application-component)
+named `request`.
+ 4. The application determines the requested [controller](/doc/guide/basics.controller)
+and [action](/doc/guide/basics.controller#action) with the help
+of an application component named `urlManager`. For this example, the controller
+is `post`, which refers to the `PostController` class; and the action is `show`,
+whose actual meaning is determined by the controller.
+ 5. The application creates an instance of the requested controller
+to further handle the user request. The controller determines that the action
+`show` refers to a method named `actionShow` in the controller class. It then
+creates and executes filters (e.g. access control, benchmarking) associated
+with this action. The action is executed if it is allowed by the filters.
+ 6. The action reads a `Post` [model](/doc/guide/basics.model) whose ID is `1` from the database.
+ 7. The action renders a [view](/doc/guide/basics.view) named `show` with the `Post` model.
+ 8. The view reads and displays the attributes of the `Post` model.
+ 9. The view executes some [widgets](/doc/guide/basics.view#widget).
+ 10. The view rendering result is embedded in a [layout](/doc/guide/basics.view#layout).
+ 11. The action completes the view rendering and displays the result to the user.
diff --git a/docs/guide/overview.md b/docs/guide/overview.md
new file mode 100644
index 0000000..9e54fd4
--- /dev/null
+++ b/docs/guide/overview.md
@@ -0,0 +1,36 @@
+What is Yii
+===========
+
+Yii is a high-performance, component-based PHP framework for developing
+large-scale Web applications rapidly. It enables maximum reusability in Web
+programming and can significantly accelerate your Web application development
+process. The name Yii (pronounced `Yee` or `[ji:]`) is an acronym for
+"**Yes It Is!**".
+
+
+Requirements
+------------
+
+To run a Yii-powered Web application, you need a Web server that supports
+PHP 5.3.?.
+
+For developers who want to use Yii, understanding object-oriented
+programming (OOP) is very helpful, because Yii is a pure OOP framework.
+
+
+What is Yii Best for?
+---------------------
+
+Yii is a generic Web programming framework that can be used for developing
+virtually any type of Web application. Because it is light-weight and
+equipped with sophisticated caching mechanisms, it is especially suited
+to high-traffic applications, such as portals, forums, content
+management systems (CMS), e-commerce systems, etc.
+
+
+How does Yii Compare with Other Frameworks?
+-------------------------------------------
+
+Like most PHP frameworks, Yii is an MVC (Model-View-Controller) framework.
+
+TBD
\ No newline at end of file
diff --git a/docs/guide/performance.md b/docs/guide/performance.md
new file mode 100644
index 0000000..9a871dc
--- /dev/null
+++ b/docs/guide/performance.md
@@ -0,0 +1,181 @@
+Performance Tuning
+==================
+
+Application performance consists of two parts. First is the framework performance
+and the second is the application itself. Yii has a pretty low performance impact
+on your application out of the box and can be fine-tuned further for production
+environment. As for the application, we'll provide some of the best practices
+along with examples on how to apply them to Yii.
+
+Preparing framework for production
+----------------------------------
+
+### Disabling Debug Mode
+
+First thing you should do before deploying your application to production environment
+is to disable debug mode. A Yii application runs in debug mode if the constant
+`YII_DEBUG` is defined as `true` in `index.php` so to disable debug the following
+should be in your `index.php`:
+
+```php
+defined('YII_DEBUG') or define('YII_DEBUG', false);
+```
+
+Debug mode is very useful during development stage, but it would impact performance
+because some components cause extra burden in debug mode. For example, the message
+logger may record additional debug information for every message being logged.
+
+### Enabling PHP opcode cache
+
+Enabling the PHP opcode cache improves any PHP application performance and lowers
+memory usage significantly. Yii is no exception. It was tested with
+[APC PHP extension](http://php.net/manual/en/book.apc.php) that caches
+and optimizes PHP intermediate code and avoids the time spent in parsing PHP
+scripts for every incoming request.
+
+### Turning on ActiveRecord database schema caching
+
+If the application is using Active Record, we should turn on the schema caching
+to save the time of parsing database schema. This can be done by setting the
+`Connection::enableSchemaCache` property to be `true` via application configuration
+`protected/config/main.php`:
+
+```php
+return array(
+ // ...
+ 'components' => array(
+ // ...
+ 'db' => array(
+ 'class' => 'yii\db\Connection',
+ 'dsn' => 'mysql:host=localhost;dbname=mydatabase',
+ 'username' => 'root',
+ 'password' => '',
+ 'enableSchemaCache' => true,
+
+ // Duration of schema cache.
+ // 'schemaCacheDuration' => 3600,
+
+ // Name of the cache component used. Default is 'cache'.
+ //'schemaCache' => 'cache',
+ ),
+ 'cache' => array(
+ 'class' => 'yii\caching\FileCache',
+ ),
+ ),
+);
+```
+
+Note that `cache` application component should be configured.
+
+### Combining and Minimizing Assets
+
+TBD
+
+### Using better storage for sessions
+
+By default PHP uses files to handle sessions. It is OK for development and
+small projects but when it comes to handling concurrent requests it's better to
+switch to another storage such as database. You can do so by configuring your
+application via `protected/config/main.php`:
+
+```php
+return array(
+ // ...
+ 'components' => array(
+ 'session' => array(
+ 'class' => 'yii\web\DbSession',
+
+ // Set the following if want to use DB component other than
+ // default 'db'.
+ // 'db' => 'mydb',
+
+ // To override default session table set the following
+ // 'sessionTable' => 'my_session',
+ ),
+ ),
+);
+```
+
+You can use `CacheSession` to store sessions using cache. Note that some
+cache storages such as memcached has no guaranteee that session data will not
+be lost leading to unexpected logouts.
+
+Improving application
+---------------------
+
+### Using Serverside Caching Techniques
+
+As described in the Caching section, Yii provides several caching solutions that
+may improve the performance of a Web application significantly. If the generation
+of some data takes long time, we can use the data caching approach to reduce the
+data generation frequency; If a portion of page remains relatively static, we
+can use the fragment caching approach to reduce its rendering frequency;
+If a whole page remains relative static, we can use the page caching approach to
+save the rendering cost for the whole page.
+
+
+### Leveraging HTTP to save procesing time and bandwidth
+
+TBD
+
+### Database Optimization
+
+Fetching data from database is often the main performance bottleneck in
+a Web application. Although using caching may alleviate the performance hit,
+it does not fully solve the problem. When the database contains enormous data
+and the cached data is invalid, fetching the latest data could be prohibitively
+expensive without proper database and query design.
+
+Design index wisely in a database. Indexing can make SELECT queries much faster,
+but it may slow down INSERT, UPDATE or DELETE queries.
+
+For complex queries, it is recommended to create a database view for it instead
+of issuing the queries inside the PHP code and asking DBMS to parse them repetitively.
+
+Do not overuse Active Record. Although Active Record is good at modelling data
+in an OOP fashion, it actually degrades performance due to the fact that it needs
+to create one or several objects to represent each row of query result. For data
+intensive applications, using DAO or database APIs at lower level could be
+a better choice.
+
+Last but not least, use LIMIT in your SELECT queries. This avoids fetching
+overwhelming data from database and exhausting the memory allocated to PHP.
+
+### Using asArray
+
+A good way to save memory and processing time on read-only pages is to use
+ActiveRecord's `asArray` method.
+
+```php
+class PostController extends Controller
+{
+ public function actionIndex()
+ {
+ $posts = Post::find()->orderBy('id DESC')->limit(100)->asArray()->all();
+ echo $this->render('index', array(
+ 'posts' => $posts,
+ ));
+ }
+}
+```
+
+In the view you should access fields of each invidual record from `$posts` as array:
+
+```php
+foreach($posts as $post) {
+ echo $post['title']."
";
+}
+```
+
+Note that you can use array notation even if `asArray` wasn't specified and you're
+working with AR objects.
+
+### Processing data in background
+
+In order to respond to user requests faster you can process heavy parts of the
+request later if there's no need for immediate response.
+
+- Cron jobs + console.
+- queues + handlers.
+
+TBD
\ No newline at end of file
diff --git a/docs/guide/query-builder.md b/docs/guide/query-builder.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/security.md b/docs/guide/security.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/template.md b/docs/guide/template.md
new file mode 100644
index 0000000..dc83d15
--- /dev/null
+++ b/docs/guide/template.md
@@ -0,0 +1,3 @@
+Template
+========
+
diff --git a/docs/guide/testing.md b/docs/guide/testing.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/theming.md b/docs/guide/theming.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/title.md b/docs/guide/title.md
new file mode 100644
index 0000000..a3e55a1
--- /dev/null
+++ b/docs/guide/title.md
@@ -0,0 +1,8 @@
+The Definitive Guide to Yii 2.0
+===============================
+
+This tutorial is released under [the Terms of Yii Documentation](http://www.yiiframework.com/doc/terms/).
+
+All Rights Reserved.
+
+2008 (c) Yii Software LLC.
diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md
new file mode 100644
index 0000000..3b24201
--- /dev/null
+++ b/docs/guide/upgrade-from-v1.md
@@ -0,0 +1,437 @@
+Upgrading from Yii 1.1
+======================
+
+In this chapter, we list the major changes introduced in Yii 2.0 since version 1.1.
+We hope this list will make it easier for you to upgrade from Yii 1.1 and quickly
+master Yii 2.0 based on your existing Yii knowledge.
+
+
+Component and Object
+--------------------
+
+Yii 2.0 breaks the `CComponent` class in 1.1 into two classes: `Object` and `Component`.
+The `Object` class is a lightweight base class that allows defining class properties
+via getters and setters. The `Component` class extends from `Object` and supports
+the event feature and the behavior feature.
+
+If your class does not need the event or behavior feature, you should consider using
+`Object` as the based class. This is usually the case for classes that represent basic
+data structures.
+
+
+Object Configuration
+--------------------
+
+The `Object` class introduces a uniform way of configuring objects. Any descendant class
+of `Object` should declare its constructor (if needed) in the following way so that
+it can be properly configured:
+
+~~~
+class MyClass extends \yii\Object
+{
+ public function __construct($param1, $param2, $config = array())
+ {
+ // ... initialization before configuration is applied
+
+ parent::__construct($config);
+ }
+
+ public function init()
+ {
+ parent::init();
+
+ // ... initialization after configuration is applied
+ }
+}
+~~~
+
+In the above, the last parameter of the constructor must take a configuration array
+which contains name-value pairs for initializing the properties at the end of the constructor.
+You can override the `init()` method to do initialization work that should be done after
+the configuration is applied.
+
+By following this convention, you will be able to create and configure a new object
+using a configuration array like the following:
+
+~~~
+$object = Yii::createObject(array(
+ 'class' => 'MyClass',
+ 'property1' => 'abc',
+ 'property2' => 'cde',
+), $param1, $param2);
+~~~
+
+
+Events
+------
+
+There is no longer the need to define an `on`-method in order to define an event in Yii 2.0.
+Instead, you can use whatever event names. To attach a handler to an event, you should
+use the `on` method now:
+
+~~~
+$component->on($eventName, $handler);
+// To detach the handler, use:
+// $component->off($eventName, $handler);
+~~~
+
+When you attach a handler, you can now associate it with some parameters which can be later
+accessed via the event parameter by the handler:
+
+~~~
+$component->on($eventName, $handler, $params);
+~~~
+
+Because of this change, you can now use "global" events. Simply trigger and attach handlers to
+an event of the application instance:
+
+~~~
+Yii::$app->on($eventName, $handler);
+....
+// this will trigger the event and cause $handler to be invoked.
+Yii::$app->trigger($eventName);
+~~~
+
+
+Path Alias
+----------
+
+Yii 2.0 expands the usage of path aliases to both file/directory paths and URLs. An alias
+must start with a `@` character so that it can be differentiated from file/directory paths and URLs.
+For example, the alias `@yii` refers to the Yii installation directory. Path aliases are
+supported in most places in the Yii core code. For example, `FileCache::cachePath` can take
+both a path alias and a normal directory path.
+
+Path alias is also closely related with class namespaces. It is recommended that a path
+alias defined for each root namespace so that you can use Yii class autoloader without
+any further configuration. For example, because `@yii` refers to the Yii installation directory,
+a class like `yii\web\Request` can be autoloaded by Yii. If you use a third party library
+such as Zend Framework, you may define a path alias `@Zend` which refers to its installation directory.
+And Yii will be able to autoload any class in this library.
+
+
+View
+----
+
+Yii 2.0 introduces a `View` class to represent the view part in the MVC pattern.
+It can be configured globally through the "view" application component. It is also
+accessible in any view file via `$this`. This is one of the biggest changes compared to 1.1:
+**`$this` in a view file no longer refers to the controller or widget object.**
+It refers to the view object that is used to render the view file. To access the controller
+or the widget object, you have to use `$this->context` now.
+
+Because you can access the view object through the "view" application component,
+you can now render a view file like the following anywhere in your code, not necessarily
+in controllers or widgets:
+
+~~~
+$content = Yii::$app->view->renderFile($viewFile, $params);
+// You can also explicitly create a new View instance to do the rendering
+// $view = new View;
+// $view->renderFile($viewFile, $params);
+~~~
+
+Also, there is no more `CClientScript` in Yii 2.0. The `View` class has taken over its role
+with significant improvements. For more details, please see the "assets" subsection.
+
+While Yii 2.0 continues to use PHP as its main template language, it comes with built-in
+support for two popular template engines: Smarty and Twig. The Prado template engine is
+no longer supported. To use these template engines, you just need to use `tpl` as the file
+extension for your Smarty views, or `twig` for Twig views. You may also configure the
+`View::renderers` property to use other template engines.
+
+
+Models
+------
+
+A model is now associated with a form name returned its `formName()` method. This is
+mainly used when using HTML forms to collect user inputs for a model. Previously in 1.1,
+this is usually hardcoded as the class name of the model.
+
+
+Yii 2.0 introduces a new method called `scenarios()` to declare which attributes require
+validation under which scenario. Child classes should overwrite `scenarios()` to return
+a list of scenarios and the corresponding attributes that need to be validated when
+`validate()` is called. For example,
+
+~~~
+public function scenarios()
+{
+ return array(
+ 'backend' => array('email', 'role'),
+ 'frontend' => array('email', '!name'),
+ );
+}
+~~~
+
+This method also determines which attributes are safe and which are not. In particular,
+given a scenario, if an attribute appears in the corresponding attribute list in `scenarios()`
+and the name is not prefixed with `!`, it is considered *safe*.
+
+Because of the above change, Yii 2.0 no longer has "safe" and "unsafe" validators.
+
+If your model only has one scenario (very common), you do not have to overwrite `scenarios()`,
+and everything will still work like the 1.1 way.
+
+
+Controllers
+-----------
+
+The `render()` and `renderPartial()` methods now return the rendering results instead of directly
+sending them out. You have to `echo` them explicitly, e.g., `echo $this->render(...);`.
+
+A new method called `populate()` is introduced to simplify the data population from user inputs
+to a model. For example,
+
+~~~
+$post = new Post;
+if ($this->populate($_POST, $model)) {...}
+// which is equivalent to:
+if (isset($_POST['Post'])) {
+ $post->attributes = $_POST['Post'];
+}
+~~~
+
+
+Themes
+------
+
+Theme works completely different in 2.0. It is now based on a path map to "translate" a source
+view into a themed view. For example, if the path map for a theme is
+`array('/www/views' => '/www/themes/basic')`, then the themed version for a view file
+`/www/views/site/index.php` will be `/www/themes/basic/site/index.php`.
+
+For this reason, theme can now be applied to any view file, even if a view rendered outside
+of the context of a controller or a widget.
+
+There is no more `CThemeManager`. Instead, `theme` is a configurable property of the "view"
+application component.
+
+
+Console Applications
+--------------------
+
+Console applications are now composed by controllers, too, like Web applications. In fact,
+console controllers and Web controllers share the same base controller class.
+
+Each console controller is like `CConsoleCommand` in 1.1. It consists of one or several
+actions. You use the `yiic ` command to execute a console command, where ``
+stands for a controller route (e.g. `sitemap/index`). Additional anonymous arguments
+are passed as the parameters to the corresponding controller action method, and named arguments
+are treated as global options declared in `globalOptions()`.
+
+Yii 2.0 supports automatic generation of command help information from comment blocks.
+
+
+I18N
+----
+
+Yii 2.0 removes date formatter and number formatter in favor of the PECL intl PHP module.
+
+Message translation is still supported, but managed via the "i18n" application component.
+The component manages a set of message sources, which allows you to use different message
+sources based on message categories. For more information, see the class documentation for `I18N`.
+
+The message translation method is changed by merging the message category into the message being
+translated. For example, `Yii::t('yii|message to be translated')`.
+
+
+
+Action Filters
+--------------
+
+Action filters are implemented via behaviors now. You should extend from `ActionFilter` to
+define a new filter. To use a filter, you should attach the filter class to the controller
+as a behavior. For example, to use the `AccessControl` filter, you should have the following
+code in a controller:
+
+~~~
+public function behaviors()
+{
+ return array(
+ 'access' => array(
+ 'class' => 'yii\web\AccessControl',
+ 'rules' => array(
+ array('allow' => true, 'actions' => array('admin'), 'roles' => array('@')),
+ array('allow' => false),
+ ),
+ ),
+ );
+}
+~~~
+
+
+Assets
+------
+
+Yii 2.0 introduces a new concept called *asset bundle*. It is a bit similar to script
+packages (managed by `CClientScript`) in 1.1, but with better support.
+
+An asset bundle is a collection of asset files (e.g. JavaScript files, CSS files, image files, etc.)
+under a directory. By registering an asset bundle via `View::registerAssetBundle()`, you
+will be able to make the assets in that bundle accessible via Web, and the current page
+will automatically contain references to the JavaScript and CSS files in that bundle.
+
+
+
+Static Helpers
+--------------
+
+Yii 2.0 introduces many commonly used static helper classes, such as `Html`, `ArrayHelper`,
+`StringHelper`. These classes are designed to be easily extended. Note that static classes
+are usually hard to be extended because of the fixed class name references. But Yii 2.0
+introduces the class map (via `Yii::$classMap`) to overcome this difficulty.
+
+
+`ActiveForm`
+------------
+
+Yii 2.0 introduces the *field* concept for building a form using `ActiveForm`. A field
+is a container consisting of a label, an input, and an error message. It is represented
+as an `ActiveField` object. Using fields, you can build a form more cleanly than before:
+
+~~~
+beginWidget('yii\widgets\ActiveForm'); ?>
+ field($model, 'username')->textInput(); ?>
+ field($model, 'password')->passwordInput(); ?>
+
+
+
+endWidget(); ?>
+~~~
+
+
+Query Builder
+-------------
+
+In 1.1, query building is scattered among several classes, including `CDbCommand`,
+`CDbCriteria`, and `CDbCommandBuilder`. Yii 2.0 uses `Query` to represent a DB query
+and `QueryBuilder` to generate SQL statements from query objects. For example,
+
+~~~
+$query = new \yii\db\Query;
+$query->select('id, name')
+ ->from('tbl_user')
+ ->limit(10);
+
+$command = $query->createCommand();
+$sql = $command->sql;
+$rows = $command->queryAll();
+~~~
+
+Best of all, such query building methods can be used together with `ActiveRecord`,
+as explained in the next sub-section.
+
+
+ActiveRecord
+------------
+
+ActiveRecord has undergone significant changes in Yii 2.0. The most important one
+is about relational ActiveRecord query. In 1.1, you have to declare the relations
+in the `relations()` method. In 2.0, this is done via getter methods that return
+an `ActiveQuery` object. For example, the following method declares an "orders" relation:
+
+~~~
+class Customer extends \yii\db\ActiveRecord
+{
+ public function getOrders()
+ {
+ return $this->hasMany('Order', array('customer_id' => 'id'));
+ }
+}
+~~~
+
+You can use `$customer->orders` to access the customer's orders. You can also
+use `$customer->getOrders()->andWhere('status=1')->all()` to perform on-the-fly
+relational query with customized query conditions.
+
+When loading relational records in an eager way, Yii 2.0 does it differently from 1.1.
+In particular, in 1.1 a JOIN query would be used to bring both the primary and the relational
+records; while in 2.0, two SQL statements are executed without using JOIN: the first
+statement brings back the primary records and the second brings back the relational records
+by filtering with the primary keys of the primary records.
+
+
+Yii 2.0 no longer uses the `model()` method when performing queries. Instead, you
+use the `find()` method like the following:
+
+~~~
+// to retrieve all *active* customers and order them by their ID:
+$customers = Customer::find()
+ ->where(array('status' => $active))
+ ->orderBy('id')
+ ->all();
+// return the customer whose PK is 1
+$customer = Customer::find(1);
+~~~
+
+The `find()` method returns an instance of `ActiveQuery` which is a subclass of `Query`.
+Therefore, you can use all query methods of `Query`.
+
+Instead of returning ActiveRecord objects, you may call `ActiveQuery::asArray()` to
+return results in terms of arrays. This is more efficient and is especially useful
+when you need to return large number of records. For example,
+
+~~~
+$customers = Customer::find()->asArray()->all();
+~~~
+
+
+By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes
+would be saved to database when you call `save()`, regardless they are changed or not,
+unless you explicitly list the attributes to save.
+
+
+Auto-quoting Table and Column Names
+------------------------------------
+
+Yii 2.0 supports automatic quoting of database table and column names. A name enclosed
+within double curly brackets is treated as a table name, and a name enclosed within
+double square brackets is treated as a column name. They will be quoted according to
+the database driver being used. For example,
+
+~~~
+$command = $connection->createCommand('SELECT [[id]] FROM {{posts}}');
+echo $command->sql; // MySQL: SELECT `id` FROM `posts`
+~~~
+
+This feature is especially useful if you are developing an application that supports
+different DBMS.
+
+
+User and Identity
+-----------------
+
+The `CWebUser` class in 1.1 is now replaced by `\yii\Web\User`, and there is no more
+`CUserIdentity` class. Instead, you should implement the `Identity` interface which
+is much more straightforward to implement. The bootstrap application provides such an example.
+
+
+URL Management
+--------------
+
+URL management is similar to 1.1. A major enhancement is that it now supports optional
+parameters. For example, if you have rule declared as follows, then it will match
+both `post/popular` and `post/1/popular`. In 1.1, you would have to use two rules to achieve
+the same goal.
+
+~~~
+array(
+ 'pattern' => 'post//',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+)
+~~~
+
+
+Response
+--------
+
+Extensions
+----------
+
+Integration with Composer
+-------------------------
+
+TBD
+
diff --git a/docs/guide/upgrade.md b/docs/guide/upgrade.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/url.md b/docs/guide/url.md
new file mode 100644
index 0000000..46bb177
--- /dev/null
+++ b/docs/guide/url.md
@@ -0,0 +1,3 @@
+URL Management
+==============
+
diff --git a/docs/guide/validation.md b/docs/guide/validation.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/guide/view.md b/docs/guide/view.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/view_renderers.md b/docs/view_renderers.md
index a953c45..e26fe83 100644
--- a/docs/view_renderers.md
+++ b/docs/view_renderers.md
@@ -18,6 +18,7 @@ array(
),
'twig' => array(
'class' => 'yii\renderers\TwigViewRenderer',
+ 'twigPath' => '@app/vendors/Twig',
),
// ...
),
@@ -26,6 +27,9 @@ array(
)
```
+Note that Smarty and Twig are not bundled with Yii and you have to download and
+unpack these yourself and then specify `twigPath` and `smartyPath` respectively.
+
Twig
----
@@ -38,6 +42,21 @@ or `$this->renderPartial()` from your controller:
echo $this->render('renderer.twig', array('username' => 'Alex'));
```
+### Additional functions
+
+Additionally to regular Twig syntax the following is available in Yii:
+
+```php
+{{ post.title }}
+```
+
+path function calls `Html::url()` internally.
+
+### Additional variables
+
+- `app` = `\Yii::$app`
+- `this` = current `View` object
+
Smarty
------
@@ -49,3 +68,18 @@ or `$this->renderPartial()` from your controller:
```php
echo $this->render('renderer.tpl', array('username' => 'Alex'));
```
+
+### Additional functions
+
+Additionally to regular Smarty syntax the following is available in Yii:
+
+```php
+{$post.title}
+```
+
+path function calls `Html::url()` internally.
+
+### Additional variables
+
+- `$app` = `\Yii::$app`
+- `$this` = current `View` object
\ No newline at end of file
diff --git a/framework/.htaccess b/framework/.htaccess
new file mode 100644
index 0000000..e019832
--- /dev/null
+++ b/framework/.htaccess
@@ -0,0 +1 @@
+deny from all
diff --git a/framework/YiiBase.php b/framework/YiiBase.php
index 9d501b1..ed975c9 100644
--- a/framework/YiiBase.php
+++ b/framework/YiiBase.php
@@ -600,6 +600,13 @@ class YiiBase
*/
public static function t($message, $params = array(), $language = null)
{
- return self::$app->getI18N()->translate($message, $params, $language);
+ if (self::$app !== null) {
+ return self::$app->getI18N()->translate($message, $params, $language);
+ } else {
+ if (strpos($message, '|') !== false && preg_match('/^([\w\-\\/\.\\\\]+)\|(.*)/', $message, $matches)) {
+ $message = $matches[2];
+ }
+ return is_array($params) ? strtr($message, $params) : $message;
+ }
}
}
diff --git a/framework/assets.php b/framework/assets.php
index 5cea992..919011b 100644
--- a/framework/assets.php
+++ b/framework/assets.php
@@ -28,4 +28,4 @@ return array(
),
'depends' => array('yii', 'yii/validation'),
),
-);
\ No newline at end of file
+);
diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js
index a011650..158ea74 100644
--- a/framework/assets/yii.activeForm.js
+++ b/framework/assets/yii.activeForm.js
@@ -23,38 +23,54 @@
};
var defaults = {
- // whether it is waiting for ajax submission result
- submitting: false
+ // the jQuery selector for the error summary
+ errorSummary: undefined,
+ // whether to perform validation before submitting the form.
+ validateOnSubmit: true,
+ // the container CSS class representing the corresponding attribute has validation error
+ errorCssClass: 'error',
+ // the container CSS class representing the corresponding attribute passes validation
+ successCssClass: 'success',
+ // the container CSS class representing the corresponding attribute is being validated
+ validatingCssClass: 'validating',
+ // the URL for performing AJAX-based validation. If not set, it will use the the form's action
+ validationUrl: undefined,
+ // a callback that is called before submitting the form. The signature of the callback should be:
+ // function ($form) { ...return false to cancel submission...}
+ beforeSubmit: undefined,
+ // a callback that is called before validating each attribute. The signature of the callback should be:
+ // function ($form, attribute, messages) { ...return false to cancel the validation...}
+ beforeValidate: undefined,
+ // the GET parameter name indicating an AJAX-based validation
+ ajaxVar: 'ajax'
+ };
+
+ var attributeDefaults = {
+ // attribute name or expression (e.g. "[0]content" for tabular input)
+ name: undefined,
+ // the jQuery selector of the container of the input field
+ container: undefined,
+ // the jQuery selector of the input field
+ input: undefined,
+ // the jQuery selector of the error tag
+ error: undefined,
+ // whether to perform validation when a change is detected on the input
+ validateOnChange: false,
+ // whether to perform validation when the user is typing.
+ validateOnType: false,
+ // number of milliseconds that the validation should be delayed when a user is typing in the input field.
+ validationDelay: 200,
+ // whether to enable AJAX-based validation.
+ enableAjaxValidation: false,
+ // function (attribute, value, messages), the client-side validation function.
+ validate: undefined,
+ // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
+ status: 0,
+ // the value of the input
+ value: undefined
};
var methods = {
- /**
- * Initializes the plugin.
- * @param attributes array attribute configurations. Each attribute may contain the following options:
- *
- * - id: 'ModelClass_attribute', // the unique attribute ID
- * - model: 'ModelClass', // the model class name
- * - name: 'name', // attribute name
- * - inputID: 'input-tag-id',
- * - errorID: 'error-tag-id',
- * - value: undefined,
- * - status: 0, // 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
- * - validationDelay: 200,
- * - validateOnChange: true,
- * - validateOnType: false,
- * - hideErrorMessage: false,
- * - inputContainer: undefined,
- * - errorCssClass: 'error',
- * - successCssClass: 'success',
- * - validatingCssClass: 'validating',
- * - enableAjaxValidation: true,
- * - enableClientValidation: true,
- * - clientValidation: undefined, // function (value, messages, attribute) | client-side validation
- * - beforeValidateAttribute: undefined, // function (form, attribute) | boolean
- * - afterValidateAttribute: undefined, // function (form, attribute, data, hasError)
- *
- * @param options object the configuration for the plugin. The following options can be set:
- */
init: function (attributes, options) {
return this.each(function () {
var $form = $(this);
@@ -62,64 +78,33 @@
return;
}
- var settings = $.extend(defaults, options || {});
+ var settings = $.extend({}, defaults, options || {});
if (settings.validationUrl === undefined) {
settings.validationUrl = $form.attr('action');
}
$.each(attributes, function (i) {
- this.value = getInputValue($form.find('#' + this.inputID));
- attributes[i] = $.extend(settings, this);
+ attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this);
});
$form.data('yiiActiveForm', {
settings: settings,
- attributes: attributes
+ attributes: attributes,
+ submitting: false,
+ validated: false
});
- bindAttributes(attributes);
+ watchAttributes($form, attributes);
- $form.bind('reset', resetForm);
+ /**
+ * Clean up error status when the form is reset.
+ * Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE.
+ */
+ $form.bind('reset.yiiActiveForm', methods.resetForm);
if (settings.validateOnSubmit) {
- $form.on('mouseup keyup', ':submit', function () {
- $form.data('submitObject', $(this));
- });
- var validated = false;
- $form.submit(function () {
- if (validated) {
- validated = false;
- return true;
- }
- if (settings.timer !== undefined) {
- clearTimeout(settings.timer);
- }
- settings.submitting = true;
- if (settings.beforeValidate === undefined || settings.beforeValidate($form)) {
- $.fn.yiiactiveform.validate($form, function (data) {
- var hasError = false;
- $.each(settings.attributes, function () {
- hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
- });
- $.fn.yiiactiveform.updateSummary($form, data);
- if (settings.afterValidate === undefined || settings.afterValidate($form, data, hasError)) {
- if (!hasError) {
- validated = true;
- var $button = $form.data('submitObject') || $form.find(':submit:first');
- // TODO: if the submission is caused by "change" event, it will not work
- if ($button.length) {
- $button.click();
- } else { // no submit button in the form
- $form.submit();
- }
- return;
- }
- }
- settings.submitting = false;
- });
- } else {
- settings.submitting = false;
- }
- return false;
+ $form.on('mouseup.yiiActiveForm keyup.yiiActiveForm', ':submit', function () {
+ $form.data('yiiActiveForm').submitObject = $(this);
});
+ $form.on('submit', methods.submitForm);
}
});
},
@@ -128,134 +113,109 @@
return this.each(function () {
$(window).unbind('.yiiActiveForm');
$(this).removeData('yiiActiveForm');
- })
- }
- };
-
- /**
- * Returns the value of the specified input element.
- * This method will perform additional checks to get proper values
- * for checkbox, radio, checkbox list and radio list.
- * @param $e jQuery the jQuery object of the input element
- * @return string the input value
- */
- var getInputValue = function ($e) {
- var type,
- c = [];
- if (!$e.length) {
- return undefined;
- }
- if ($e[0].tagName.toLowerCase() === 'div') {
- $e.find(':checked').each(function () {
- c.push(this.value);
});
- return c.join(',');
- }
- type = $e.attr('type');
- if (type === 'checkbox' || type === 'radio') {
- return $e.filter(':checked').val();
- } else {
- return $e.val();
- }
- };
+ },
- var bindAttributes = function (attributes) {
- $.each(attributes, function (i, attribute) {
- if (this.validateOnChange) {
- $form.find('#' + this.inputID).change(function () {
- validateAttribute(attribute, false);
- }).blur(function () {
- if (attribute.status !== 2 && attribute.status !== 3) {
- validateAttribute(attribute, !attribute.status);
- }
- });
- }
- if (this.validateOnType) {
- $form.find('#' + this.inputID).keyup(function () {
- if (attribute.value !== getAFValue($(this))) {
- validateAttribute(attribute, false);
- }
- });
- }
- });
- };
+ options: function() {
+ return this.data('yiiActiveForm').settings;
+ },
- /**
- * Performs the ajax validation request.
- * This method is invoked internally to trigger the ajax validation.
- * @param form jquery the jquery representation of the form
- * @param successCallback function the function to be invoked if the ajax request succeeds
- * @param errorCallback function the function to be invoked if the ajax request fails
- */
- var validateForm = function (form, successCallback, errorCallback) {
- var $form = $(form),
- settings = $form.data('settings'),
- needAjaxValidation = false,
- messages = {};
- $.each(settings.attributes, function () {
- var value,
- msg = [];
- if (this.clientValidation !== undefined && (settings.submitting || this.status === 2 || this.status === 3)) {
- value = getInputValue($form.find('#' + this.inputID));
- this.clientValidation(value, msg, this);
- if (msg.length) {
- messages[this.id] = msg;
- }
+ submitForm: function () {
+ var $form = $(this),
+ data = $form.data('yiiActiveForm');
+ if (data.validated) {
+ // continue submitting the form since validation passes
+ data.validated = false;
+ return true;
}
- if (this.enableAjaxValidation && !msg.length && (settings.submitting || this.status === 2 || this.status === 3)) {
- needAjaxValidation = true;
- }
- });
- if (!needAjaxValidation || settings.submitting && !$.isEmptyObject(messages)) {
- if (settings.submitting) {
- // delay callback so that the form can be submitted without problem
- setTimeout(function () {
- successCallback(messages);
- }, 200);
+ if (data.settings.timer !== undefined) {
+ clearTimeout(data.settings.timer);
+ }
+ data.submitting = true;
+ if (!data.settings.beforeSubmit || data.settings.beforeSubmit($form)) {
+ validate($form, function (messages) {
+ var hasError = false;
+ $.each(data.attributes, function () {
+ hasError = updateInput($form, this, messages) || hasError;
+ });
+ updateSummary($form, messages);
+ if (!hasError) {
+ data.validated = true;
+ var $button = data.submitObject || $form.find(':submit:first');
+ // TODO: if the submission is caused by "change" event, it will not work
+ if ($button.length) {
+ $button.click();
+ } else {
+ // no submit button in the form
+ $form.submit();
+ }
+ return;
+ }
+ data.submitting = false;
+ }, function () {
+ data.submitting = false;
+ });
} else {
- successCallback(messages);
+ data.submitting = false;
}
- return;
- }
+ return false;
+ },
- var $button = $form.data('submitObject'),
- extData = '&' + settings.ajaxVar + '=' + $form.attr('id');
- if ($button && $button.length) {
- extData += '&' + $button.attr('name') + '=' + $button.attr('value');
+ resetForm: function () {
+ var $form = $(this);
+ var data = $form.data('yiiActiveForm');
+ // Because we bind directly to a form reset event instead of a reset button (that may not exist),
+ // when this function is executed form input values have not been reset yet.
+ // Therefore we do the actual reset work through setTimeout.
+ setTimeout(function () {
+ $.each(data.attributes, function () {
+ // Without setTimeout() we would get the input values that are not reset yet.
+ this.value = getValue($form, this);
+ this.status = 0;
+ var $container = $form.find(this.container);
+ $container.removeClass(
+ data.settings.validatingCssClass + ' ' +
+ data.settings.errorCssClass + ' ' +
+ data.settings.successCssClass
+ );
+ $container.find(this.error).html('');
+ });
+ $form.find(data.settings.summary).hide().find('ul').html('');
+ }, 1);
}
+ };
- $.ajax({
- url: settings.validationUrl,
- type: $form.attr('method'),
- data: $form.serialize() + extData,
- dataType: 'json',
- success: function (data) {
- if (data !== null && typeof data === 'object') {
- $.each(settings.attributes, function () {
- if (!this.enableAjaxValidation) {
- delete data[this.id];
- }
- });
- successCallback($.extend({}, messages, data));
- } else {
- successCallback(messages);
- }
- },
- error: function () {
- if (errorCallback !== undefined) {
- errorCallback();
- }
+ var watchAttributes = function ($form, attributes) {
+ $.each(attributes, function (i, attribute) {
+ var $input = findInput($form, attribute);
+ if (attribute.validateOnChange) {
+ $input.on('change.yiiActiveForm', function () {
+ validateAttribute($form, attribute, false);
+ }).on('blur.yiiActiveForm', function () {
+ if (attribute.status == 0 || attribute.status == 1) {
+ validateAttribute($form, attribute, !attribute.status);
+ }
+ });
+ }
+ if (attribute.validateOnType) {
+ $input.on('keyup.yiiActiveForm', function () {
+ if (attribute.value !== getValue($form, attribute)) {
+ validateAttribute($form, attribute, false);
+ }
+ });
}
});
};
- var validateAttribute = function (attribute, forceValidate) {
+ var validateAttribute = function ($form, attribute, forceValidate) {
+ var data = $form.data('yiiActiveForm');
+
if (forceValidate) {
attribute.status = 2;
}
- $.each(attributes, function () {
- if (this.value !== getInputValue($form.find('#' + this.inputID))) {
+ $.each(data.attributes, function () {
+ if (this.value !== getValue($form, this)) {
this.status = 2;
forceValidate = true;
}
@@ -264,158 +224,164 @@
return;
}
- if (settings.timer !== undefined) {
- clearTimeout(settings.timer);
+ if (data.settings.timer !== undefined) {
+ clearTimeout(data.settings.timer);
}
- settings.timer = setTimeout(function () {
- if (settings.submitting || $form.is(':hidden')) {
+ data.settings.timer = setTimeout(function () {
+ if (data.submitting || $form.is(':hidden')) {
return;
}
- if (attribute.beforeValidateAttribute === undefined || attribute.beforeValidateAttribute($form, attribute)) {
- $.each(settings.attributes, function () {
- if (this.status === 2) {
- this.status = 3;
- $.fn.yiiactiveform.getInputContainer(this, $form).addClass(this.validatingCssClass);
- }
- });
- $.fn.yiiactiveform.validate($form, function (data) {
- var hasError = false;
- $.each(settings.attributes, function () {
- if (this.status === 2 || this.status === 3) {
- hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
- }
- });
- if (attribute.afterValidateAttribute !== undefined) {
- attribute.afterValidateAttribute($form, attribute, data, hasError);
+ $.each(data.attributes, function () {
+ if (this.status === 2) {
+ this.status = 3;
+ $form.find(this.container).addClass(data.settings.validatingCssClass);
+ }
+ });
+ validate($form, function (messages) {
+ var hasError = false;
+ $.each(data.attributes, function () {
+ if (this.status === 2 || this.status === 3) {
+ hasError = updateInput($form, this, messages) || hasError;
}
});
- }
- }, attribute.validationDelay);
- };
-
- var resetForm = function () {
- /*
- * In case of resetting the form we need to reset error messages
- * NOTE1: $form.reset - does not exist
- * NOTE2: $form.on('reset', ...) does not work
- */
- /*
- * because we bind directly to a form reset event, not to a reset button (that could or could not exist),
- * when this function is executed form elements values have not been reset yet,
- * because of that we use the setTimeout
- */
- setTimeout(function () {
- $.each(settings.attributes, function () {
- this.status = 0;
- var $error = $form.find('#' + this.errorID),
- $container = $.fn.yiiactiveform.getInputContainer(this, $form);
-
- $container.removeClass(
- this.validatingCssClass + ' ' +
- this.errorCssClass + ' ' +
- this.successCssClass
- );
-
- $error.html('').hide();
-
- /*
- * without the setTimeout() we would get here the current entered value before the reset instead of the reseted value
- */
- this.value = getAFValue($form.find('#' + this.inputID));
- });
- /*
- * If the form is submited (non ajax) with errors, labels and input gets the class 'error'
- */
- $form.find('label, input').each(function () {
- $(this).removeClass(settings.errorCss);
});
- $('#' + settings.summaryID).hide().find('ul').html('');
- //.. set to initial focus on reset
- if (settings.focus !== undefined && !window.location.hash) {
- $form.find(settings.focus).focus();
- }
- }, 1);
+ }, data.settings.validationDelay);
};
-
-
+
/**
- * Returns the container element of the specified attribute.
- * @param attribute object the configuration for a particular attribute.
- * @param form the form jQuery object
- * @return jQuery the jQuery representation of the container
+ * Performs validation.
+ * @param $form jQuery the jquery representation of the form
+ * @param successCallback function the function to be invoked if the validation completes
+ * @param errorCallback function the function to be invoked if the ajax validation request fails
*/
- var getInputContainer = function (attribute, form) {
- if (attribute.inputContainer === undefined) {
- return form.find('#' + attribute.inputID).closest('div');
+ var validate = function ($form, successCallback, errorCallback) {
+ var data = $form.data('yiiActiveForm'),
+ needAjaxValidation = false,
+ messages = {};
+
+ $.each(data.attributes, function () {
+ if (data.submitting || this.status === 2 || this.status === 3) {
+ var msg = [];
+ if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) {
+ if (this.validate) {
+ this.validate(this, getValue($form, this), msg);
+ }
+ if (msg.length) {
+ messages[this.name] = msg;
+ } else if (this.enableAjaxValidation) {
+ needAjaxValidation = true;
+ }
+ }
+ }
+ });
+
+ if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) {
+ // Perform ajax validation when at least one input needs it.
+ // If the validation is triggered by form submission, ajax validation
+ // should be done only when all inputs pass client validation
+ var $button = data.submitObject,
+ extData = '&' + data.settings.ajaxVar + '=' + $form.attr('id');
+ if ($button && $button.length && $button.attr('name')) {
+ extData += '&' + $button.attr('name') + '=' + $button.attr('value');
+ }
+ $.ajax({
+ url: data.settings.validationUrl,
+ type: $form.attr('method'),
+ data: $form.serialize() + extData,
+ dataType: 'json',
+ success: function (msgs) {
+ if (msgs !== null && typeof msgs === 'object') {
+ $.each(data.attributes, function () {
+ if (!this.enableAjaxValidation) {
+ delete msgs[this.name];
+ }
+ });
+ successCallback($.extend({}, messages, msgs));
+ } else {
+ successCallback(messages);
+ }
+ },
+ error: errorCallback
+ });
+ } else if (data.submitting) {
+ // delay callback so that the form can be submitted without problem
+ setTimeout(function () {
+ successCallback(messages);
+ }, 200);
} else {
- return form.find(attribute.inputContainer).filter(':has("#' + attribute.inputID + '")');
+ successCallback(messages);
}
};
/**
- * updates the error message and the input container for a particular attribute.
+ * Updates the error message and the input container for a particular attribute.
+ * @param $form the form jQuery object
* @param attribute object the configuration for a particular attribute.
- * @param messages array the json data obtained from the ajax validation request
- * @param form the form jQuery object
+ * @param messages array the validation error messages
* @return boolean whether there is a validation error for the specified attribute
*/
- var updateInput = function (attribute, messages, form) {
- attribute.status = 1;
- var $error, $container,
- hasError = false,
- $el = form.find('#' + attribute.inputID),
- errorCss = form.data('settings').errorCss;
-
- if ($el.length) {
- hasError = messages !== null && $.isArray(messages[attribute.id]) && messages[attribute.id].length > 0;
- $error = form.find('#' + attribute.errorID);
- $container = $.fn.yiiactiveform.getInputContainer(attribute, form);
-
- $container.removeClass(
- attribute.validatingCssClass + ' ' +
- attribute.errorCssClass + ' ' +
- attribute.successCssClass
- );
- $container.find('label, input').each(function () {
- $(this).removeClass(errorCss);
- });
+ var updateInput = function ($form, attribute, messages) {
+ var data = $form.data('yiiActiveForm'),
+ $input = findInput($form, attribute),
+ hasError = false;
+ attribute.status = 1;
+ if ($input.length) {
+ hasError = messages && $.isArray(messages[attribute.name]) && messages[attribute.name].length;
+ var $container = $form.find(attribute.container);
+ var $error = $container.find(attribute.error);
if (hasError) {
- $error.html(messages[attribute.id][0]);
- $container.addClass(attribute.errorCssClass);
- } else if (attribute.enableAjaxValidation || attribute.clientValidation) {
- $container.addClass(attribute.successCssClass);
- }
- if (!attribute.hideErrorMessage) {
- $error.toggle(hasError);
+ $error.html(messages[attribute.name][0]);
+ $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass)
+ .addClass(data.settings.errorCssClass);
+ } else {
+ $error.html('');
+ $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ')
+ .addClass(data.settings.successCssClass);
}
-
- attribute.value = getAFValue($el);
+ attribute.value = getValue($form, attribute);
}
return hasError;
};
/**
- * updates the error summary, if any.
- * @param form jquery the jquery representation of the form
- * @param messages array the json data obtained from the ajax validation request
+ * Updates the error summary.
+ * @param $form the form jQuery object
+ * @param messages array the validation error messages
*/
- var updateSummary = function (form, messages) {
- var settings = $(form).data('settings'),
+ var updateSummary = function ($form, messages) {
+ var data = $form.data('yiiActiveForm'),
+ $summary = $form.find(data.settings.errorSummary),
content = '';
- if (settings.summaryID === undefined) {
- return;
- }
- if (messages) {
- $.each(settings.attributes, function () {
- if ($.isArray(messages[this.id])) {
- $.each(messages[this.id], function (j, message) {
- content = content + '' + message + '';
- });
+
+ if ($summary.length && messages) {
+ $.each(data.attributes, function () {
+ if ($.isArray(messages[this.name]) && messages[this.name].length) {
+ content += '' + messages[this.name][0] + '';
}
});
+ $summary.toggle(content !== '').find('ul').html(content);
+ }
+ };
+
+ var getValue = function ($form, attribute) {
+ var $input = findInput($form, attribute);
+ var type = $input.attr('type');
+ if (type === 'checkbox' || type === 'radio') {
+ return $input.filter(':checked').val();
+ } else {
+ return $input.val();
+ }
+ };
+
+ var findInput = function ($form, attribute) {
+ var $input = $form.find(attribute.input);
+ if ($input.length && $input[0].tagName.toLowerCase() === 'div') {
+ // checkbox list or radio list
+ return $input.find('input');
+ } else {
+ return $input;
}
- $('#' + settings.summaryID).toggle(content !== '').find('ul').html(content);
};
})(window.jQuery);
\ No newline at end of file
diff --git a/framework/assets/yii.js b/framework/assets/yii.js
index 3a23763..1e847c4 100644
--- a/framework/assets/yii.js
+++ b/framework/assets/yii.js
@@ -7,24 +7,59 @@
* @author Qiang Xue
* @since 2.0
*/
+
+/**
+ * yii is the root module for all Yii JavaScript modules.
+ * It implements a mechanism of organizing JavaScript code in modules through the function "yii.initModule()".
+ *
+ * Each module should be named as "x.y.z", where "x" stands for the root module (for the Yii core code, this is "yii").
+ *
+ * A module may be structured as follows:
+ *
+ * ~~~
+ * yii.sample = (function($) {
+ * var pub = {
+ * // whether this module is currently active. If false, init() will not be called for this module
+ * // it will also not be called for all its child modules. If this property is undefined, it means true.
+ * isActive: true,
+ * init: function() {
+ * // ... module initialization code go here ...
+ * },
+ *
+ * // ... other public functions and properties go here ...
+ * };
+ *
+ * // ... private functions and properties go here ...
+ *
+ * return pub;
+ * });
+ * ~~~
+ *
+ * Using this structure, you can define public and private functions/properties for a module.
+ * Private functions/properties are only visible within the module, while public functions/properties
+ * may be accessed outside of the module. For example, you can access "yii.sample.init()".
+ *
+ * You must call "yii.initModule()" once for the root module of all your modules.
+ */
yii = (function ($) {
var pub = {
- version: '2.0'
+ version: '2.0',
+ initModule: function (module) {
+ if (module.isActive === undefined || module.isActive) {
+ if ($.isFunction(module.init)) {
+ module.init();
+ }
+ $.each(module, function () {
+ if ($.isPlainObject(this)) {
+ pub.initModule(this);
+ }
+ });
+ }
+ }
};
return pub;
})(jQuery);
-jQuery(document).ready(function ($) {
- // call the init() method of every module
- var init = function (module) {
- if ($.isFunction(module.init) && (module.trigger == undefined || $(module.trigger).length)) {
- module.init();
- }
- $.each(module, function () {
- if ($.isPlainObject(this)) {
- init(this);
- }
- });
- };
- init(yii);
+jQuery(document).ready(function () {
+ yii.initModule(yii);
});
diff --git a/framework/assets/yii.validation.js b/framework/assets/yii.validation.js
index acfc261..fd098be 100644
--- a/framework/assets/yii.validation.js
+++ b/framework/assets/yii.validation.js
@@ -1,7 +1,7 @@
/**
* Yii validation module.
*
- * This is the JavaScript widget used by the yii\widgets\ActiveForm widget.
+ * This JavaScript module provides the validation methods for the built-in validaotrs.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
@@ -11,7 +11,188 @@
*/
yii.validation = (function ($) {
- var pub = {
+ var isEmpty = function (value, trim) {
+ return value === null || value === undefined || value == []
+ || value === '' || trim && $.trim(value) === '';
+ };
+
+ return {
+ required: function (value, messages, options) {
+ var valid = false;
+ if (options.requiredValue === undefined) {
+ if (options.strict && value !== undefined || !options.strict && !isEmpty(value, true)) {
+ valid = true;
+ }
+ } else if (!options.strict && value == options.requiredValue || options.strict && value === options.requiredValue) {
+ valid = true;
+ }
+
+ if (!valid) {
+ messages.push(options.message);
+ }
+ },
+
+ boolean: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+ var valid = !options.strict && (value == options.trueValue || value == options.falseValue)
+ || options.strict && (value === options.trueValue || value === options.falseValue);
+
+ if (!valid) {
+ messages.push(options.message);
+ }
+ },
+
+ string: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (typeof value !== 'string') {
+ messages.push(options.message);
+ return;
+ }
+
+ if (options.min !== undefined && value.length < options.min) {
+ messages.push(options.tooShort);
+ }
+ if (options.max !== undefined && value.length > options.max) {
+ messages.push(options.tooLong);
+ }
+ if (options.is !== undefined && value.length != options.is) {
+ messages.push(options.is);
+ }
+ },
+
+ number: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (typeof value === 'string' && !value.match(options.pattern)) {
+ messages.push(options.message);
+ return;
+ }
+
+ if (options.min !== undefined && value < options.min) {
+ messages.push(options.tooSmall);
+ }
+ if (options.max !== undefined && value > options.max) {
+ messages.push(options.tooBig);
+ }
+ },
+
+ range: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+ var valid = !options.not && $.inArray(value, options.range)
+ || options.not && !$.inArray(value, options.range);
+
+ if (!valid) {
+ messages.push(options.message);
+ }
+ },
+
+ regularExpression: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (!options.not && !value.match(options.pattern) || options.not && value.match(options.pattern)) {
+ messages.push(options.message)
+ }
+ },
+
+ email: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ var valid = value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern));
+
+ if (!valid) {
+ messages.push(options.message);
+ }
+ },
+
+ url: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (options.defaultScheme && !value.match(/:\/\//)) {
+ value = options.defaultScheme + '://' + value;
+ }
+
+ if (!value.match(options.pattern)) {
+ messages.push(options.message);
+ }
+ },
+
+ captcha: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ // CAPTCHA may be updated via AJAX and the updated hash is stored in body data
+ var hash = $('body').data(options.hashKey);
+ if (hash == null) {
+ hash = options.hash;
+ } else {
+ hash = hash[options.caseSensitive ? 0 : 1];
+ }
+ var v = options.caseSensitive ? value : value.toLowerCase();
+ for (var i = v.length - 1, h = 0; i >= 0; --i) {
+ h += v.charCodeAt(i);
+ }
+ if (h != hash) {
+ messages.push(options.message);
+ }
+ },
+
+ compare: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ var compareValue, valid = true;
+ if (options.compareAttribute === undefined) {
+ compareValue = options.compareValue;
+ } else {
+ compareValue = $('#' + options.compareAttribute).val();
+ }
+ switch (options.operator) {
+ case '==':
+ valid = value == compareValue;
+ break;
+ case '===':
+ valid = value === compareValue;
+ break;
+ case '!=':
+ valid = value != compareValue;
+ break;
+ case '!==':
+ valid = value !== compareValue;
+ break;
+ case '>':
+ valid = value > compareValue;
+ break;
+ case '>=':
+ valid = value >= compareValue;
+ break;
+ case '<':
+ valid = value < compareValue;
+ break;
+ case '<=':
+ valid = value <= compareValue;
+ break;
+ }
+
+ if (!valid) {
+ messages.push(options.message);
+ }
+ }
};
- return pub;
})(jQuery);
diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php
index 1f82e5d..d69c0fe 100644
--- a/framework/base/ActionFilter.php
+++ b/framework/base/ActionFilter.php
@@ -87,4 +87,4 @@ class ActionFilter extends Behavior
{
return !in_array($action->id, $this->except, true) && (empty($this->only) || in_array($action->id, $this->only, true));
}
-}
\ No newline at end of file
+}
diff --git a/framework/base/Application.php b/framework/base/Application.php
index 6dca5cf..5b92f76 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -306,6 +306,15 @@ class Application extends Module
}
/**
+ * @return null|Component
+ * @todo
+ */
+ public function getAuthManager()
+ {
+ return $this->getComponent('auth');
+ }
+
+ /**
* Registers the core application components.
* @see setComponents
*/
diff --git a/framework/base/Component.php b/framework/base/Component.php
index 80259e7..8e75835 100644
--- a/framework/base/Component.php
+++ b/framework/base/Component.php
@@ -90,6 +90,7 @@ class Component extends Object
// as behavior: attach behavior
$name = trim(substr($name, 3));
$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
+ return;
} else {
// behavior property
$this->ensureBehaviors();
@@ -496,6 +497,7 @@ class Component extends Object
*/
public function detachBehavior($name)
{
+ $this->ensureBehaviors();
if (isset($this->_behaviors[$name])) {
$behavior = $this->_behaviors[$name];
unset($this->_behaviors[$name]);
@@ -511,6 +513,7 @@ class Component extends Object
*/
public function detachBehaviors()
{
+ $this->ensureBehaviors();
if ($this->_behaviors !== null) {
foreach ($this->_behaviors as $name => $behavior) {
$this->detachBehavior($name);
diff --git a/framework/base/Exception.php b/framework/base/Exception.php
index 9ee698b..7d26bd0 100644
--- a/framework/base/Exception.php
+++ b/framework/base/Exception.php
@@ -22,4 +22,4 @@ class Exception extends \Exception
{
return \Yii::t('yii|Exception');
}
-}
\ No newline at end of file
+}
diff --git a/framework/base/View.php b/framework/base/View.php
index a72982e..af1f0d5 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -72,23 +72,21 @@ class View extends Component
/**
* @var array a list of available renderers indexed by their corresponding supported file extensions.
* Each renderer may be a view renderer object or the configuration for creating the renderer object.
- * For example,
- *
- * ~~~
- * array(
- * 'tpl' => array(
- * 'class' => 'yii\renderers\SmartyRenderer',
- * ),
- * 'twig' => array(
- * 'class' => 'yii\renderers\TwigRenderer',
- * ),
- * )
- * ~~~
+ * The default setting supports both Smarty and Twig (their corresponding file extension is "tpl"
+ * and "twig" respectively. Please refer to [[SmartyRenderer]] and [[TwigRenderer]] on how to install
+ * the needed libraries for these template engines.
*
* If no renderer is available for the given view file, the view file will be treated as a normal PHP
* and rendered via [[renderPhpFile()]].
*/
- public $renderers = array();
+ public $renderers = array(
+ 'tpl' => array(
+ 'class' => 'yii\renderers\SmartyRenderer',
+ ),
+ 'twig' => array(
+ 'class' => 'yii\renderers\TwigRenderer',
+ ),
+ );
/**
* @var Theme|array the theme object or the configuration array for creating the theme object.
* If not set, it means theming is not enabled.
@@ -744,10 +742,10 @@ class View extends Component
{
$lines = array();
if (!empty($this->metaTags)) {
- $lines[] = implode("\n", $this->cssFiles);
+ $lines[] = implode("\n", $this->metaTags);
}
if (!empty($this->linkTags)) {
- $lines[] = implode("\n", $this->cssFiles);
+ $lines[] = implode("\n", $this->linkTags);
}
if (!empty($this->cssFiles)) {
$lines[] = implode("\n", $this->cssFiles);
@@ -797,4 +795,4 @@ class View extends Component
}
return implode("\n", $lines);
}
-}
\ No newline at end of file
+}
diff --git a/framework/base/ViewEvent.php b/framework/base/ViewEvent.php
index cac7be4..f1ee7b9 100644
--- a/framework/base/ViewEvent.php
+++ b/framework/base/ViewEvent.php
@@ -41,4 +41,4 @@ class ViewEvent extends Event
$this->viewFile = $viewFile;
parent::__construct($config);
}
-}
\ No newline at end of file
+}
diff --git a/framework/base/Widget.php b/framework/base/Widget.php
index 13e6d30..c0c524f 100644
--- a/framework/base/Widget.php
+++ b/framework/base/Widget.php
@@ -83,7 +83,8 @@ class Widget extends Component
*/
public function render($view, $params = array())
{
- return $this->view->render($view, $params, $this);
+ $viewFile = $this->findViewFile($view);
+ return $this->view->renderFile($viewFile, $params, $this);
}
/**
@@ -133,4 +134,4 @@ class Widget extends Component
return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file;
}
-}
\ No newline at end of file
+}
diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php
index 7edeb19..1b56a99 100644
--- a/framework/caching/Cache.php
+++ b/framework/caching/Cache.php
@@ -349,4 +349,4 @@ abstract class Cache extends Component implements \ArrayAccess
{
$this->delete($key);
}
-}
\ No newline at end of file
+}
diff --git a/framework/caching/Dependency.php b/framework/caching/Dependency.php
index feb8c07..d1428fc 100644
--- a/framework/caching/Dependency.php
+++ b/framework/caching/Dependency.php
@@ -49,4 +49,4 @@ abstract class Dependency extends \yii\base\Object
* @return mixed the data needed to determine if dependency has been changed.
*/
abstract protected function generateDependencyData();
-}
\ No newline at end of file
+}
diff --git a/framework/caching/ExpressionDependency.php b/framework/caching/ExpressionDependency.php
index e13c962..ec4736c 100644
--- a/framework/caching/ExpressionDependency.php
+++ b/framework/caching/ExpressionDependency.php
@@ -14,24 +14,36 @@ namespace yii\caching;
* The dependency is reported as unchanged if and only if the result of the expression is
* the same as the one evaluated when storing the data to cache.
*
+ * A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
+ * please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php).
+ *
* @author Qiang Xue
* @since 2.0
*/
class ExpressionDependency extends Dependency
{
/**
- * @var string the PHP expression whose result is used to determine the dependency.
+ * @var string the string representation of a PHP expression whose result is used to determine the dependency.
+ * A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
+ * please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php).
*/
public $expression;
+ /**
+ * @var mixed custom data associated with this dependency. In [[expression]], you may compare the value of
+ * this property with the latest data to determine if the dependency has changed or not.
+ */
+ public $data;
/**
* Constructor.
* @param string $expression the PHP expression whose result is used to determine the dependency.
+ * @param mixed $data the custom data associated with this dependency
* @param array $config name-value pairs that will be used to initialize the object properties
*/
- public function __construct($expression = 'true', $config = array())
+ public function __construct($expression = 'true', $data = null, $config = array())
{
$this->expression = $expression;
+ $this->data = $data;
parent::__construct($config);
}
diff --git a/framework/caching/MemCacheServer.php b/framework/caching/MemCacheServer.php
index 105137e..dc0de08 100644
--- a/framework/caching/MemCacheServer.php
+++ b/framework/caching/MemCacheServer.php
@@ -46,4 +46,4 @@ class MemCacheServer extends \yii\base\Object
* @var boolean if the server should be flagged as online upon a failure. This is used by memcache only.
*/
public $status = true;
-}
\ No newline at end of file
+}
diff --git a/framework/caching/WinCache.php b/framework/caching/WinCache.php
index e9bf9f5..eed580d 100644
--- a/framework/caching/WinCache.php
+++ b/framework/caching/WinCache.php
@@ -89,4 +89,4 @@ class WinCache extends Cache
{
return wincache_ucache_clear();
}
-}
\ No newline at end of file
+}
diff --git a/framework/console/Controller.php b/framework/console/Controller.php
index c7c5642..2eaf4b8 100644
--- a/framework/console/Controller.php
+++ b/framework/console/Controller.php
@@ -147,4 +147,4 @@ class Controller extends \yii\base\Controller
{
return array();
}
-}
\ No newline at end of file
+}
diff --git a/framework/console/controllers/AppController.php b/framework/console/controllers/AppController.php
index a47acfe..17f7420 100644
--- a/framework/console/controllers/AppController.php
+++ b/framework/console/controllers/AppController.php
@@ -321,4 +321,4 @@ class AppController extends Controller
closedir($handle);
return $list;
}
-}
\ No newline at end of file
+}
diff --git a/framework/console/controllers/AssetController.php b/framework/console/controllers/AssetController.php
index 71a2cae..aab489b 100644
--- a/framework/console/controllers/AssetController.php
+++ b/framework/console/controllers/AssetController.php
@@ -350,4 +350,4 @@ return array(
EOD;
file_put_contents($configFile, $template);
}
-}
\ No newline at end of file
+}
diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php
index 74c354b..82bd6fe 100644
--- a/framework/console/controllers/HelpController.php
+++ b/framework/console/controllers/HelpController.php
@@ -418,4 +418,4 @@ class HelpController extends Controller
$name = $required ? "$name (required)" : $name;
return $doc === '' ? $name : "$name: $doc";
}
-}
\ No newline at end of file
+}
diff --git a/framework/console/webapp/config.php b/framework/console/webapp/config.php
deleted file mode 100644
index 112fb18..0000000
--- a/framework/console/webapp/config.php
+++ /dev/null
@@ -1,17 +0,0 @@
- array(
- 'index.php' => array(
- 'handler' => function($source) use ($controller) {
- return $controller->replaceRelativePath($source, realpath(YII_PATH.'/yii.php'), 'yii');
- },
- 'permissions' => 0777,
- ),
- 'protected/runtime' => array(
- 'permissions' => 0755,
- ),
- ),
-);
\ No newline at end of file
diff --git a/framework/console/webapp/default/index.php b/framework/console/webapp/default/index.php
deleted file mode 100644
index b84e257..0000000
--- a/framework/console/webapp/default/index.php
+++ /dev/null
@@ -1,10 +0,0 @@
-run();
\ No newline at end of file
diff --git a/framework/console/webapp/default/protected/config/main.php b/framework/console/webapp/default/protected/config/main.php
deleted file mode 100644
index 795811e..0000000
--- a/framework/console/webapp/default/protected/config/main.php
+++ /dev/null
@@ -1,20 +0,0 @@
- 'webapp',
- 'name' => 'My Web Application',
-
- 'components' => array(
- // uncomment the following to use a MySQL database
- /*
- 'db' => array(
- 'class' => 'yii\db\Connection',
- 'dsn' => 'mysql:host=localhost;dbname=testdrive',
- 'username' => 'root',
- 'password' => '',
- ),
- */
- 'cache' => array(
- 'class' => 'yii\caching\DummyCache',
- ),
- ),
-);
\ No newline at end of file
diff --git a/framework/console/webapp/default/protected/controllers/SiteController.php b/framework/console/webapp/default/protected/controllers/SiteController.php
deleted file mode 100644
index b47b93c..0000000
--- a/framework/console/webapp/default/protected/controllers/SiteController.php
+++ /dev/null
@@ -1,15 +0,0 @@
-render('index', array(
- 'name' => 'Qiang',
- ));
- }
-}
\ No newline at end of file
diff --git a/framework/console/webapp/default/protected/views/layouts/main.php b/framework/console/webapp/default/protected/views/layouts/main.php
deleted file mode 100644
index 5c883e6..0000000
--- a/framework/console/webapp/default/protected/views/layouts/main.php
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
- title)?>
-
-
- title)?>
-
-
-
-
-
-
\ No newline at end of file
diff --git a/framework/console/webapp/default/protected/views/site/index.php b/framework/console/webapp/default/protected/views/site/index.php
deleted file mode 100644
index 0fb8784..0000000
--- a/framework/console/webapp/default/protected/views/site/index.php
+++ /dev/null
@@ -1 +0,0 @@
-Hello, !
\ No newline at end of file
diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php
index 43c3059..3999600 100644
--- a/framework/db/ActiveQuery.php
+++ b/framework/db/ActiveQuery.php
@@ -88,7 +88,8 @@ class ActiveQuery extends Query
{
if (method_exists($this->modelClass, $name)) {
array_unshift($params, $this);
- return call_user_func_array(array($this->modelClass, $name), $params);
+ call_user_func_array(array($this->modelClass, $name), $params);
+ return $this;
} else {
return parent::__call($name, $params);
}
diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php
index c547f1a..23d3ac6 100644
--- a/framework/db/ActiveRelation.php
+++ b/framework/db/ActiveRelation.php
@@ -266,7 +266,7 @@ class ActiveRelation extends ActiveQuery
{
$attributes = array_keys($this->link);
$values = array();
- if (count($attributes) ===1) {
+ if (count($attributes) === 1) {
// single key
$attribute = reset($this->link);
foreach ($models as $model) {
diff --git a/framework/db/Connection.php b/framework/db/Connection.php
index 797508a..03b10a8 100644
--- a/framework/db/Connection.php
+++ b/framework/db/Connection.php
@@ -66,7 +66,7 @@ use yii\caching\Cache;
* // ... executing other SQL statements ...
* $transaction->commit();
* } catch(Exception $e) {
- * $transaction->rollBack();
+ * $transaction->rollback();
* }
* ~~~
*
@@ -517,7 +517,7 @@ class Connection extends Component
public function quoteSql($sql)
{
$db = $this;
- return preg_replace_callback('/(\\{\\{([\w\-\. ]+)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
+ return preg_replace_callback('/(\\{\\{([%\w\-\. ]+)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
function($matches) use($db) {
if (isset($matches[3])) {
return $db->quoteColumnName($matches[3]);
diff --git a/framework/db/Exception.php b/framework/db/Exception.php
index ad97b5a..b7a60b4 100644
--- a/framework/db/Exception.php
+++ b/framework/db/Exception.php
@@ -41,4 +41,4 @@ class Exception extends \yii\base\Exception
{
return \Yii::t('yii|Database Exception');
}
-}
\ No newline at end of file
+}
diff --git a/framework/db/Expression.php b/framework/db/Expression.php
index 4ebcd5f..77e9f60 100644
--- a/framework/db/Expression.php
+++ b/framework/db/Expression.php
@@ -57,4 +57,4 @@ class Expression extends \yii\base\Object
{
return $this->expression;
}
-}
\ No newline at end of file
+}
diff --git a/framework/db/Migration.php b/framework/db/Migration.php
index ce2cf97..f51e597 100644
--- a/framework/db/Migration.php
+++ b/framework/db/Migration.php
@@ -64,14 +64,14 @@ class Migration extends \yii\base\Component
$transaction = $this->db->beginTransaction();
try {
if ($this->safeUp() === false) {
- $transaction->rollBack();
+ $transaction->rollback();
return false;
}
$transaction->commit();
} catch (\Exception $e) {
echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
- $transaction->rollBack();
+ $transaction->rollback();
return false;
}
return null;
@@ -89,14 +89,14 @@ class Migration extends \yii\base\Component
$transaction = $this->db->beginTransaction();
try {
if ($this->safeDown() === false) {
- $transaction->rollBack();
+ $transaction->rollback();
return false;
}
$transaction->commit();
} catch (\Exception $e) {
echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
- $transaction->rollBack();
+ $transaction->rollback();
return false;
}
return null;
@@ -368,4 +368,4 @@ class Migration extends \yii\base\Component
$this->db->createCommand()->dropIndex($name, $table)->execute();
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
-}
\ No newline at end of file
+}
diff --git a/framework/db/Query.php b/framework/db/Query.php
index 2239f5d..6f76265 100644
--- a/framework/db/Query.php
+++ b/framework/db/Query.php
@@ -483,7 +483,7 @@ class Query extends \yii\base\Component
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `array('id' => Query::SORT_ASC ASC, 'name' => Query::SORT_DESC)`).
+ * (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
@@ -499,7 +499,7 @@ class Query extends \yii\base\Component
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
- * (e.g. `array('id' => Query::SORT_ASC ASC, 'name' => Query::SORT_DESC)`).
+ * (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
diff --git a/framework/db/StaleObjectException.php b/framework/db/StaleObjectException.php
index 860c9fc..0a04bd3 100644
--- a/framework/db/StaleObjectException.php
+++ b/framework/db/StaleObjectException.php
@@ -20,4 +20,4 @@ class StaleObjectException extends Exception
{
return \Yii::t('yii|Stale Object Exception');
}
-}
\ No newline at end of file
+}
diff --git a/framework/db/Transaction.php b/framework/db/Transaction.php
index d66c38e..195a8c8 100644
--- a/framework/db/Transaction.php
+++ b/framework/db/Transaction.php
@@ -25,7 +25,7 @@ use yii\base\InvalidConfigException;
* //.... other SQL executions
* $transaction->commit();
* } catch(Exception $e) {
- * $transaction->rollBack();
+ * $transaction->rollback();
* }
* ~~~
*
@@ -42,14 +42,14 @@ class Transaction extends \yii\base\Object
public $db;
/**
* @var boolean whether this transaction is active. Only an active transaction
- * can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started.
+ * can [[commit()]] or [[rollback()]]. This property is set true when the transaction is started.
*/
private $_active = false;
/**
* Returns a value indicating whether this transaction is active.
* @return boolean whether this transaction is active. Only an active transaction
- * can [[commit()]] or [[rollBack()]].
+ * can [[commit()]] or [[rollback()]].
*/
public function getIsActive()
{
diff --git a/framework/helpers/ArrayHelper.php b/framework/helpers/ArrayHelper.php
index 3061717..d58341c 100644
--- a/framework/helpers/ArrayHelper.php
+++ b/framework/helpers/ArrayHelper.php
@@ -16,4 +16,4 @@ namespace yii\helpers;
*/
class ArrayHelper extends base\ArrayHelper
{
-}
\ No newline at end of file
+}
diff --git a/framework/helpers/FileHelper.php b/framework/helpers/FileHelper.php
index 3fb24e1..04ce4e1 100644
--- a/framework/helpers/FileHelper.php
+++ b/framework/helpers/FileHelper.php
@@ -18,4 +18,4 @@ namespace yii\helpers;
*/
class FileHelper extends base\FileHelper
{
-}
\ No newline at end of file
+}
diff --git a/framework/helpers/JsExpression.php b/framework/helpers/JsExpression.php
new file mode 100644
index 0000000..5a1f9bd
--- /dev/null
+++ b/framework/helpers/JsExpression.php
@@ -0,0 +1,45 @@
+
+ * @since 2.0
+ */
+class JsExpression extends Object
+{
+ /**
+ * @var string the JavaScript expression represented by this object
+ */
+ public $expression;
+
+ /**
+ * Constructor.
+ * @param string $expression the JavaScript expression represented by this object
+ * @param array $config additional configurations for this object
+ */
+ public function __construct($expression, $config = array())
+ {
+ $this->expression = $expression;
+ parent::__construct($config);
+ }
+
+ /**
+ * The PHP magic function converting an object into a string.
+ * @return string the JavaScript expression.
+ */
+ public function __toString()
+ {
+ return $this->expression;
+ }
+}
diff --git a/framework/helpers/Json.php b/framework/helpers/Json.php
new file mode 100644
index 0000000..5e77c3f
--- /dev/null
+++ b/framework/helpers/Json.php
@@ -0,0 +1,18 @@
+
+ * @since 2.0
+ */
+class Json extends base\Json
+{
+
+}
diff --git a/framework/helpers/SecurityHelper.php b/framework/helpers/SecurityHelper.php
index d3cb2ad..d16e7e6 100644
--- a/framework/helpers/SecurityHelper.php
+++ b/framework/helpers/SecurityHelper.php
@@ -26,4 +26,4 @@ namespace yii\helpers;
*/
class SecurityHelper extends base\SecurityHelper
{
-}
\ No newline at end of file
+}
diff --git a/framework/helpers/VarDumper.php b/framework/helpers/VarDumper.php
index 2659188..59a1718 100644
--- a/framework/helpers/VarDumper.php
+++ b/framework/helpers/VarDumper.php
@@ -25,4 +25,4 @@ namespace yii\helpers;
*/
class VarDumper extends base\VarDumper
{
-}
\ No newline at end of file
+}
diff --git a/framework/helpers/base/ArrayHelper.php b/framework/helpers/base/ArrayHelper.php
index 9870542..86445d7 100644
--- a/framework/helpers/base/ArrayHelper.php
+++ b/framework/helpers/base/ArrayHelper.php
@@ -236,15 +236,17 @@ class ArrayHelper
* To sort by multiple keys, provide an array of keys here.
* @param boolean|array $ascending whether to sort in ascending or descending order. When
* sorting by multiple keys with different ascending orders, use an array of ascending flags.
- * @param integer|array $sortFlag the PHP sort flag. Valid values include:
- * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, and `SORT_STRING | SORT_FLAG_CASE`. The last
- * value is for sorting strings in case-insensitive manner. Please refer to
- * See [PHP manual](http://php.net/manual/en/function.sort.php) for more details.
- * When sorting by multiple keys with different sort flags, use an array of sort flags.
+ * @param integer|array $sortFlag the PHP sort flag. Valid values include
+ * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`.
+ * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php)
+ * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags.
+ * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter
+ * is used only when `$sortFlag` is `SORT_STRING`.
+ * When sorting by multiple keys with different case sensitivities, use an array of boolean values.
* @throws InvalidParamException if the $ascending or $sortFlag parameters do not have
* correct number of elements as that of $key.
*/
- public static function multisort(&$array, $key, $ascending = true, $sortFlag = SORT_REGULAR)
+ public static function multisort(&$array, $key, $ascending = true, $sortFlag = SORT_REGULAR, $caseSensitive = true)
{
$keys = is_array($key) ? $key : array($key);
if (empty($keys) || empty($array)) {
@@ -259,20 +261,30 @@ class ArrayHelper
if (is_scalar($sortFlag)) {
$sortFlag = array_fill(0, $n, $sortFlag);
} elseif (count($sortFlag) !== $n) {
- throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.');
+ throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.');
+ }
+ if (is_scalar($caseSensitive)) {
+ $caseSensitive = array_fill(0, $n, $caseSensitive);
+ } elseif (count($caseSensitive) !== $n) {
+ throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.');
}
$args = array();
foreach ($keys as $i => $key) {
$flag = $sortFlag[$i];
- if ($flag == (SORT_STRING | SORT_FLAG_CASE)) {
- $flag = SORT_STRING;
- $column = array();
- foreach (static::getColumn($array, $key) as $k => $value) {
- $column[$k] = strtolower($value);
+ $cs = $caseSensitive[$i];
+ if (!$cs && ($flag === SORT_STRING)) {
+ if (defined('SORT_FLAG_CASE')) {
+ $flag = $flag | SORT_FLAG_CASE;
+ $args[] = static::getColumn($array, $key);
+ } else {
+ $column = array();
+ foreach (static::getColumn($array, $key) as $k => $value) {
+ $column[$k] = mb_strtolower($value);
+ }
+ $args[] = $column;
}
- $args[] = $column;
} else {
- $args[] = static::getColumn($array, $key);
+ $args[] = static::getColumn($array, $key);
}
$args[] = $ascending[$i] ? SORT_ASC : SORT_DESC;
$args[] = $flag;
@@ -337,4 +349,4 @@ class ArrayHelper
}
return $d;
}
-}
\ No newline at end of file
+}
diff --git a/framework/helpers/base/FileHelper.php b/framework/helpers/base/FileHelper.php
index 2f62f43..954c86e 100644
--- a/framework/helpers/base/FileHelper.php
+++ b/framework/helpers/base/FileHelper.php
@@ -169,4 +169,4 @@ class FileHelper
}
closedir($handle);
}
-}
\ No newline at end of file
+}
diff --git a/framework/helpers/base/Html.php b/framework/helpers/base/Html.php
index fd06226..15db823 100644
--- a/framework/helpers/base/Html.php
+++ b/framework/helpers/base/Html.php
@@ -127,13 +127,15 @@ class Html
* Encodes special characters into HTML entities.
* The [[yii\base\Application::charset|application charset]] will be used for encoding.
* @param string $content the content to be encoded
+ * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false,
+ * HTML entities in `$content` will not be further encoded.
* @return string the encoded content
* @see decode
* @see http://www.php.net/manual/en/function.htmlspecialchars.php
*/
- public static function encode($content)
+ public static function encode($content, $doubleEncode = true)
{
- return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset);
+ return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode);
}
/**
@@ -375,7 +377,8 @@ class Html
*/
public static function mailto($text, $email = null, $options = array())
{
- return static::a($text, 'mailto:' . ($email === null ? $text : $email), $options);
+ $options['href'] = 'mailto:' . ($email === null ? $text : $email);
+ return static::tag('a', $text, $options);
}
/**
@@ -896,6 +899,7 @@ class Html
$attribute = static::getAttributeName($attribute);
$label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute));
$for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute);
+ unset($options['label'], $options['for']);
return static::label($label, $for, $options);
}
diff --git a/framework/helpers/base/Json.php b/framework/helpers/base/Json.php
new file mode 100644
index 0000000..c92e208
--- /dev/null
+++ b/framework/helpers/base/Json.php
@@ -0,0 +1,107 @@
+
+ * @since 2.0
+ */
+class Json
+{
+ /**
+ * Encodes the given value into a JSON string.
+ * The method enhances `json_encode()` by supporting JavaScript expressions.
+ * In particular, the method will not encode a JavaScript expression that is
+ * represented in terms of a [[JsExpression]] object.
+ * @param mixed $value the data to be encoded
+ * @param integer $options the encoding options. For more details please refer to
+ * [[http://www.php.net/manual/en/function.json-encode.php]]
+ * @return string the encoding result
+ */
+ public static function encode($value, $options = 0)
+ {
+ $expressions = array();
+ $value = static::processData($value, $expressions);
+ $json = json_encode($value, $options);
+ return $expressions === array() ? $json : strtr($json, $expressions);
+ }
+
+ /**
+ * Decodes the given JSON string into a PHP data structure.
+ * @param string $json the JSON string to be decoded
+ * @param boolean $asArray whether to return objects in terms of associative arrays.
+ * @return mixed the PHP data
+ * @throws InvalidParamException if there is any decoding error
+ */
+ public static function decode($json, $asArray = true)
+ {
+ if (is_array($json)) {
+ throw new InvalidParamException('Invalid JSON data.');
+ }
+ $decode = json_decode((string)$json, $asArray);
+ switch (json_last_error()) {
+ case JSON_ERROR_NONE:
+ break;
+ case JSON_ERROR_DEPTH:
+ throw new InvalidParamException('The maximum stack depth has been exceeded.');
+ case JSON_ERROR_CTRL_CHAR:
+ throw new InvalidParamException('Control character error, possibly incorrectly encoded.');
+ case JSON_ERROR_SYNTAX:
+ throw new InvalidParamException('Syntax error.');
+ case JSON_ERROR_STATE_MISMATCH:
+ throw new InvalidParamException('Invalid or malformed JSON.');
+ case JSON_ERROR_UTF8:
+ throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.');
+ default:
+ throw new InvalidParamException('Unknown JSON decoding error.');
+ }
+
+ return $decode;
+ }
+
+ /**
+ * Pre-processes the data before sending it to `json_encode()`.
+ * @param mixed $data the data to be processed
+ * @param array $expressions collection of JavaScript expressions
+ * @return mixed the processed data
+ */
+ protected static function processData($data, &$expressions)
+ {
+ if (is_array($data)) {
+ foreach ($data as $key => $value) {
+ if (is_array($value) || is_object($value)) {
+ $data[$key] = static::processData($value, $expressions);
+ }
+ }
+ return $data;
+ } elseif (is_object($data)) {
+ if ($data instanceof JsExpression) {
+ $token = '!{[' . count($expressions) . ']}!';
+ $expressions['"' . $token . '"'] = $data->expression;
+ return $token;
+ }
+ $result = array();
+ foreach ($data as $key => $value) {
+ if (is_array($value) || is_object($value)) {
+ $result[$key] = static::processData($value, $expressions);
+ } else {
+ $result[$key] = $value;
+ }
+ }
+ return $result;
+ } else {
+ return $data;
+ }
+ }
+}
diff --git a/framework/helpers/base/SecurityHelper.php b/framework/helpers/base/SecurityHelper.php
index 6ba48ba..3f69fee 100644
--- a/framework/helpers/base/SecurityHelper.php
+++ b/framework/helpers/base/SecurityHelper.php
@@ -42,7 +42,8 @@ class SecurityHelper
public static function encrypt($data, $key)
{
$module = static::openCryptModule();
- $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module));
+ // 192-bit (24 bytes) key size
+ $key = StringHelper::substr($key, 0, 24);
srand();
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
mcrypt_generic_init($module, $key, $iv);
@@ -63,7 +64,8 @@ class SecurityHelper
public static function decrypt($data, $key)
{
$module = static::openCryptModule();
- $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module));
+ // 192-bit (24 bytes) key size
+ $key = StringHelper::substr($key, 0, 24);
$ivSize = mcrypt_enc_get_iv_size($module);
$iv = StringHelper::substr($data, 0, $ivSize);
mcrypt_generic_init($module, $key, $iv);
@@ -148,7 +150,8 @@ class SecurityHelper
if (!extension_loaded('mcrypt')) {
throw new InvalidConfigException('The mcrypt PHP extension is not installed.');
}
- $module = @mcrypt_module_open('rijndael-256', '', MCRYPT_MODE_CBC, '');
+ // AES uses a 128-bit block size
+ $module = @mcrypt_module_open('rijndael-128', '', 'cbc', '');
if ($module === false) {
throw new Exception('Failed to initialize the mcrypt module.');
}
@@ -164,11 +167,11 @@ class SecurityHelper
*
* ~~~
* // generates the hash (usually done during user registration or when the password is changed)
- * $hash = SecurityHelper::hashPassword($password);
+ * $hash = SecurityHelper::generatePasswordHash($password);
* // ...save $hash in database...
*
* // during login, validate if the password entered is correct using $hash fetched from database
- * if (PasswordHelper::verifyPassword($password, $hash) {
+ * if (SecurityHelper::verifyPassword($password, $hash) {
* // password is good
* } else {
* // password is bad
@@ -214,7 +217,7 @@ class SecurityHelper
throw new InvalidParamException('Password must be a string and cannot be empty.');
}
- if (!preg_match('/^\$2[axy]\$(\d\d)\$[\./0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) {
+ if (!preg_match('/^\$2[axy]\$(\d\d)\$[\.\/0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) {
throw new InvalidParamException('Hash is invalid.');
}
@@ -269,4 +272,4 @@ class SecurityHelper
$salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22));
return $salt;
}
-}
\ No newline at end of file
+}
diff --git a/framework/helpers/base/VarDumper.php b/framework/helpers/base/VarDumper.php
index fe15d98..730aafe 100644
--- a/framework/helpers/base/VarDumper.php
+++ b/framework/helpers/base/VarDumper.php
@@ -39,7 +39,7 @@ class VarDumper
*/
public static function dump($var, $depth = 10, $highlight = false)
{
- echo self::dumpAsString($var, $depth, $highlight);
+ echo static::dumpAsString($var, $depth, $highlight);
}
/**
@@ -116,7 +116,7 @@ class VarDumper
} elseif (self::$_depth <= $level) {
self::$_output .= get_class($var) . '(...)';
} else {
- $id = self::$_objects[] = $var;
+ $id = array_push(self::$_objects, $var);
$className = get_class($var);
$members = (array)$var;
$spaces = str_repeat(' ', $level * 4);
@@ -131,4 +131,4 @@ class VarDumper
break;
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/i18n/PhpMessageSource.php b/framework/i18n/PhpMessageSource.php
index 1ada44a..f62939f 100644
--- a/framework/i18n/PhpMessageSource.php
+++ b/framework/i18n/PhpMessageSource.php
@@ -76,4 +76,4 @@ class PhpMessageSource extends MessageSource
return array();
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/i18n/data/plurals.php b/framework/i18n/data/plurals.php
index 52c733b..468f7e2 100644
--- a/framework/i18n/data/plurals.php
+++ b/framework/i18n/data/plurals.php
@@ -624,4 +624,4 @@ return array (
array (
0 => 'in_array(fmod($n,10),array(1,2))||fmod($n,20)==0',
),
-);
\ No newline at end of file
+);
diff --git a/framework/logging/EmailTarget.php b/framework/logging/EmailTarget.php
index 4c84739..bb02e34 100644
--- a/framework/logging/EmailTarget.php
+++ b/framework/logging/EmailTarget.php
@@ -69,4 +69,4 @@ class EmailTarget extends Target
}
mail($sentTo, $subject, $body, implode("\r\n", $headers));
}
-}
\ No newline at end of file
+}
diff --git a/framework/logging/ProfileTarget.php b/framework/logging/ProfileTarget.php
index 2b6ffe6..335e172 100644
--- a/framework/logging/ProfileTarget.php
+++ b/framework/logging/ProfileTarget.php
@@ -189,4 +189,4 @@ class CProfileLogRoute extends CWebLogRoute
$total += $delta;
return array($token, $calls, $min, $max, $total);
}
-}
\ No newline at end of file
+}
diff --git a/framework/logging/Target.php b/framework/logging/Target.php
index e76e8ac..311334d 100644
--- a/framework/logging/Target.php
+++ b/framework/logging/Target.php
@@ -89,7 +89,7 @@ abstract class Target extends \yii\base\Component
*/
public function collect($messages, $final)
{
- $this->_messages = array($this->_messages, $this->filterMessages($messages));
+ $this->_messages = array_merge($this->_messages, $this->filterMessages($messages));
$count = count($this->_messages);
if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
if (($context = $this->getContextMessage()) !== '') {
diff --git a/framework/logging/WebTarget.php b/framework/logging/WebTarget.php
index b71e1a2..c98fd9f 100644
--- a/framework/logging/WebTarget.php
+++ b/framework/logging/WebTarget.php
@@ -58,4 +58,4 @@ class CWebLogRoute extends CLogRoute
$viewFile = YII_PATH . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $view . '.php';
include($app->findLocalizedFile($viewFile, 'en'));
}
-}
\ No newline at end of file
+}
diff --git a/framework/renderers/SmartyViewRenderer.php b/framework/renderers/SmartyViewRenderer.php
index ab9125a..29d7b97 100644
--- a/framework/renderers/SmartyViewRenderer.php
+++ b/framework/renderers/SmartyViewRenderer.php
@@ -13,6 +13,7 @@ use Yii;
use Smarty;
use yii\base\View;
use yii\base\ViewRenderer;
+use yii\helpers\Html;
/**
* SmartyViewRenderer allows you to use Smarty templates in views.
@@ -48,6 +49,34 @@ class SmartyViewRenderer extends ViewRenderer
$this->smarty = new Smarty();
$this->smarty->setCompileDir(Yii::getAlias($this->compilePath));
$this->smarty->setCacheDir(Yii::getAlias($this->cachePath));
+
+ $this->smarty->registerPlugin('function', 'path', array($this, 'smarty_function_path'));
+ }
+
+ /**
+ * Smarty template function to get a path for using in links
+ *
+ * Usage is the following:
+ *
+ * {path route='blog/view' alias=$post.alias user=$user.id}
+ *
+ * where route is Yii route and the rest of parameters are passed as is.
+ *
+ * @param $params
+ * @param \Smarty_Internal_Template $template
+ *
+ * @return string
+ */
+ public function smarty_function_path($params, \Smarty_Internal_Template $template)
+ {
+ if(!isset($params['route'])) {
+ trigger_error("path: missing 'route' parameter");
+ }
+
+ array_unshift($params, $params['route']) ;
+ unset($params['route']);
+
+ return Html::url($params);
}
/**
@@ -67,6 +96,10 @@ class SmartyViewRenderer extends ViewRenderer
$ext = pathinfo($file, PATHINFO_EXTENSION);
/** @var \Smarty_Internal_Template $template */
$template = $this->smarty->createTemplate($file, null, null, $params, true);
+
+ $template->assign('app', \Yii::$app);
+ $template->assign('this', $view);
+
return $template->fetch();
}
-}
\ No newline at end of file
+}
diff --git a/framework/renderers/TwigViewRenderer.php b/framework/renderers/TwigViewRenderer.php
index b92998d..79faa9f 100644
--- a/framework/renderers/TwigViewRenderer.php
+++ b/framework/renderers/TwigViewRenderer.php
@@ -12,6 +12,7 @@ namespace yii\renderers;
use Yii;
use yii\base\View;
use yii\base\ViewRenderer;
+use yii\helpers\Html;
/**
* TwigViewRenderer allows you to use Twig templates in views.
@@ -53,6 +54,12 @@ class TwigViewRenderer extends ViewRenderer
$this->twig = new \Twig_Environment($loader, array_merge(array(
'cache' => Yii::getAlias($this->cachePath),
), $this->options));
+
+ $this->twig->addFunction('path', new \Twig_Function_Function(function($path, $args = array()){
+ return Html::url(array_merge(array($path), $args));
+ }));
+
+ $this->twig->addGlobal('app', \Yii::$app);
}
/**
@@ -69,6 +76,7 @@ class TwigViewRenderer extends ViewRenderer
*/
public function render($view, $file, $params)
{
+ $this->twig->addGlobal('this', $view);
return $this->twig->render(file_get_contents($file), $params);
}
}
diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php
index e336cf5..1420739 100644
--- a/framework/validators/BooleanValidator.php
+++ b/framework/validators/BooleanValidator.php
@@ -8,6 +8,7 @@
namespace yii\validators;
use Yii;
+use yii\helpers\Html;
/**
* BooleanValidator checks if the attribute value is a boolean value.
@@ -82,16 +83,23 @@ class BooleanValidator extends Validator
*/
public function clientValidateAttribute($object, $attribute)
{
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
- '{true}' => $this->trueValue,
- '{false}' => $this->falseValue,
- ));
- return "
-if(" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . "value!=" . json_encode($this->trueValue) . " && value!=" . json_encode($this->falseValue) . ") {
- messages.push(" . json_encode($message) . ");
-}
-";
+ $options = array(
+ 'trueValue' => $this->trueValue,
+ 'falseValue' => $this->falseValue,
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ '{true}' => $this->trueValue,
+ '{false}' => $this->falseValue,
+ ))),
+ );
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+ if ($this->strict) {
+ $options['strict'] = 1;
+ }
+
+ return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');';
}
}
diff --git a/framework/validators/CaptchaValidator.php b/framework/validators/CaptchaValidator.php
index ebb0039..4eba9df 100644
--- a/framework/validators/CaptchaValidator.php
+++ b/framework/validators/CaptchaValidator.php
@@ -9,6 +9,7 @@ namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
+use yii\helpers\Html;
/**
* CaptchaValidator validates that the attribute value is the same as the verification code displayed in the CAPTCHA.
@@ -94,33 +95,22 @@ class CaptchaValidator extends Validator
public function clientValidateAttribute($object, $attribute)
{
$captcha = $this->getCaptchaAction();
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
- ));
$code = $captcha->getVerifyCode(false);
$hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code));
- $js = "
-var hash = $('body').data(' {$this->captchaAction}.hash');
-if (hash == null)
- hash = $hash;
-else
- hash = hash[" . ($this->caseSensitive ? 0 : 1) . "];
-for(var i=value.length-1, h=0; i >= 0; --i) h+=value." . ($this->caseSensitive ? '' : 'toLowerCase().') . "charCodeAt(i);
-if(h != hash) {
- messages.push(" . json_encode($message) . ");
-}
-";
-
+ $options = array(
+ 'hash' => $hash,
+ 'hashKey' => 'yiiCaptcha/' . $this->captchaAction,
+ 'caseSensitive' => $this->caseSensitive,
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ ))),
+ );
if ($this->skipOnEmpty) {
- $js = "
-if($.trim(value)!='') {
- $js
-}
-";
+ $options['skipOnEmpty'] = 1;
}
- return $js;
+ return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');';
}
}
diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php
index 1df09c4..68504e5 100644
--- a/framework/validators/CompareValidator.php
+++ b/framework/validators/CompareValidator.php
@@ -9,6 +9,7 @@ namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
+use yii\helpers\Html;
/**
* CompareValidator compares the specified attribute value with another value and validates if they are equal.
@@ -58,6 +59,15 @@ class CompareValidator extends Validator
* - `<=`: validates to see if the value being validated is less than or equal to the value being compared with.
*/
public $operator = '=';
+ /**
+ * @var string the user-defined error message. It may contain the following placeholders which
+ * will be replaced accordingly by the validator:
+ *
+ * - `{attribute}`: the label of the attribute being validated
+ * - `{value}`: the value of the attribute being validated
+ * - `{compareValue}`: the value or the attribute label to be compared with
+ */
+ public $message;
/**
@@ -172,24 +182,27 @@ class CompareValidator extends Validator
*/
public function clientValidateAttribute($object, $attribute)
{
+ $options = array('operator' => $this->operator);
+
if ($this->compareValue !== null) {
- $compareLabel = $this->compareValue;
- $compareValue = json_encode($this->compareValue);
+ $options['compareValue'] = $this->compareValue;
+ $compareValue = $this->compareValue;
} else {
$compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
- $compareValue = "\$('#" . (CHtml::activeId($object, $compareAttribute)) . "').val()";
- $compareLabel = $object->getAttributeLabel($compareAttribute);
+ $compareValue = $object->getAttributeLabel($compareAttribute);
+ $options['compareAttribute'] = Html::getInputId($object, $compareAttribute);
}
- $condition = "value {$this->operator} $compareValue";
- $message = strtr($this->message, array(
+
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+
+ $options['message'] = Html::encode(strtr($this->message, array(
'{attribute}' => $object->getAttributeLabel($attribute),
- '{compareValue}' => $compareLabel,
- ));
+ '{value}' => $object->$attribute,
+ '{compareValue}' => $compareValue,
+ )));
- return "
-if (" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . $condition . ") {
- messages.push(" . json_encode($message) . ");
-}
-";
+ return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');';
}
}
diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php
index e498975..ad74dd6 100644
--- a/framework/validators/EmailValidator.php
+++ b/framework/validators/EmailValidator.php
@@ -8,6 +8,9 @@
namespace yii\validators;
use Yii;
+use yii\helpers\Html;
+use yii\helpers\JsExpression;
+use yii\helpers\Json;
/**
* EmailValidator validates that the attribute value is a valid email address.
@@ -100,20 +103,19 @@ class EmailValidator extends Validator
*/
public function clientValidateAttribute($object, $attribute)
{
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
- ));
-
- $condition = "!value.match( {$this->pattern})";
- if ($this->allowName) {
- $condition .= " && !value.match( {$this->fullPattern})";
+ $options = array(
+ 'pattern' => new JsExpression($this->pattern),
+ 'fullPattern' => new JsExpression($this->fullPattern),
+ 'allowName' => $this->allowName,
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ ))),
+ );
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
}
- return "
-if(" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . $condition . ") {
- messages.push(" . json_encode($message) . ");
-}
-";
+ return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');';
}
}
diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php
index b3de0b2..ebe6cad 100644
--- a/framework/validators/FileValidator.php
+++ b/framework/validators/FileValidator.php
@@ -8,7 +8,6 @@
namespace yii\validators;
use Yii;
-use yii\helpers\FileHelper;
use yii\web\UploadedFile;
/**
@@ -193,11 +192,11 @@ class FileValidator extends Validator
break;
case UPLOAD_ERR_CANT_WRITE:
$this->addError($object, $attribute, $this->message);
- Yii::warning('Failed to write the uploaded file to disk: ', $file->getName(), __METHOD__);
+ Yii::warning('Failed to write the uploaded file to disk: ' . $file->getName(), __METHOD__);
break;
case UPLOAD_ERR_EXTENSION:
$this->addError($object, $attribute, $this->message);
- Yii::warning('File upload was stopped by some PHP extension: ', $file->getName(), __METHOD__);
+ Yii::warning('File upload was stopped by some PHP extension: ' . $file->getName(), __METHOD__);
break;
default:
break;
@@ -249,4 +248,4 @@ class FileValidator extends Validator
return (int)$sizeStr;
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/validators/InlineValidator.php b/framework/validators/InlineValidator.php
index 3689a2f..8af5bbc 100644
--- a/framework/validators/InlineValidator.php
+++ b/framework/validators/InlineValidator.php
@@ -96,4 +96,4 @@ class InlineValidator extends Validator
return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php
index 915419e..c0f81cd 100644
--- a/framework/validators/NumberValidator.php
+++ b/framework/validators/NumberValidator.php
@@ -8,6 +8,9 @@
namespace yii\validators;
use Yii;
+use yii\helpers\Html;
+use yii\helpers\JsExpression;
+use yii\helpers\Json;
/**
* NumberValidator validates that the attribute value is a number.
@@ -116,48 +119,36 @@ class NumberValidator extends Validator
public function clientValidateAttribute($object, $attribute)
{
$label = $object->getAttributeLabel($attribute);
- $message = strtr($this->message, array(
- '{attribute}' => $label,
- ));
+ $value = $object->$attribute;
+
+ $options = array(
+ 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern),
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $label,
+ '{value}' => $value,
+ ))),
+ );
- $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
- $js = "
-if(!value.match($pattern)) {
- messages.push(" . json_encode($message) . ");
-}
-";
if ($this->min !== null) {
- $tooSmall = strtr($this->tooSmall, array(
+ $options['min'] = $this->min;
+ $options['tooSmall'] = Html::encode(strtr($this->tooSmall, array(
'{attribute}' => $label,
+ '{value}' => $value,
'{min}' => $this->min,
- ));
-
- $js .= "
-if(value<{$this->min}) {
- messages.push(" . json_encode($tooSmall) . ");
-}
-";
+ )));
}
if ($this->max !== null) {
- $tooBig = strtr($this->tooBig, array(
+ $options['max'] = $this->max;
+ $options['tooBig'] = Html::encode(strtr($this->tooBig, array(
'{attribute}' => $label,
+ '{value}' => $value,
'{max}' => $this->max,
- ));
- $js .= "
-if(value>{$this->max}) {
- messages.push(" . json_encode($tooBig) . ");
-}
-";
+ )));
}
-
if ($this->skipOnEmpty) {
- $js = "
-if(jQuery.trim(value)!='') {
- $js
-}
-";
+ $options['skipOnEmpty'] = 1;
}
- return $js;
+ return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');';
}
-}
\ No newline at end of file
+}
diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php
index 18742ae..2a9e15f 100644
--- a/framework/validators/RangeValidator.php
+++ b/framework/validators/RangeValidator.php
@@ -9,6 +9,7 @@ namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
+use yii\helpers\Html;
/**
* RangeValidator validates that the attribute value is among a list of values.
@@ -60,9 +61,7 @@ class RangeValidator extends Validator
public function validateAttribute($object, $attribute)
{
$value = $object->$attribute;
- if (!$this->not && !in_array($value, $this->range, $this->strict)) {
- $this->addError($object, $attribute, $this->message);
- } elseif ($this->not && in_array($value, $this->range, $this->strict)) {
+ if (!$this->validateValue($value)) {
$this->addError($object, $attribute, $this->message);
}
}
@@ -86,21 +85,22 @@ class RangeValidator extends Validator
*/
public function clientValidateAttribute($object, $attribute)
{
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
- ));
-
$range = array();
foreach ($this->range as $value) {
$range[] = (string)$value;
}
- $range = json_encode($range);
+ $options = array(
+ 'range' => $range,
+ 'not' => $this->not,
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ ))),
+ );
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
- return "
-if (" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . ($this->not ? "$.inArray(value, $range)>=0" : "$.inArray(value, $range)<0") . ") {
- messages.push(" . json_encode($message) . ");
-}
-";
+ return 'yii.validation.range(value, messages, ' . json_encode($options) . ');';
}
}
diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php
index 6c69be3..79a1a3c 100644
--- a/framework/validators/RegularExpressionValidator.php
+++ b/framework/validators/RegularExpressionValidator.php
@@ -9,6 +9,9 @@ namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
+use yii\helpers\Html;
+use yii\helpers\JsExpression;
+use yii\helpers\Json;
/**
* RegularExpressionValidator validates that the attribute value matches the specified [[pattern]].
@@ -81,11 +84,6 @@ class RegularExpressionValidator extends Validator
*/
public function clientValidateAttribute($object, $attribute)
{
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
- ));
-
$pattern = $this->pattern;
$pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern);
$deliminator = substr($pattern, 0, 1);
@@ -100,10 +98,18 @@ class RegularExpressionValidator extends Validator
$pattern .= preg_replace('/[^igm]/', '', $flag);
}
- return "
-if (" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . ($this->not ? '' : '!') . "value.match($pattern)) {
- messages.push(" . json_encode($message) . ");
-}
-";
+ $options = array(
+ 'pattern' => new JsExpression($pattern),
+ 'not' => $this->not,
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ ))),
+ );
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+
+ return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');';
}
}
diff --git a/framework/validators/RequiredValidator.php b/framework/validators/RequiredValidator.php
index f4746e6..4c14a8d 100644
--- a/framework/validators/RequiredValidator.php
+++ b/framework/validators/RequiredValidator.php
@@ -8,6 +8,7 @@
namespace yii\validators;
use Yii;
+use yii\helpers\Html;
/**
* RequiredValidator validates that the specified attribute does not have null or empty value.
@@ -39,6 +40,15 @@ class RequiredValidator extends Validator
* to check if the attribute value is empty.
*/
public $strict = false;
+ /**
+ * @var string the user-defined error message. It may contain the following placeholders which
+ * will be replaced accordingly by the validator:
+ *
+ * - `{attribute}`: the label of the attribute being validated
+ * - `{value}`: the value of the attribute being validated
+ * - `{requiredValue}`: the value of [[requiredValue]]
+ */
+ public $message;
/**
* Initializes the validator.
@@ -60,13 +70,10 @@ class RequiredValidator extends Validator
*/
public function validateAttribute($object, $attribute)
{
- $value = $object->$attribute;
- if ($this->requiredValue === null) {
- if ($this->strict && $value === null || !$this->strict && $this->isEmpty($value, true)) {
+ if (!$this->validateValue($object->$attribute)) {
+ if ($this->requiredValue === null) {
$this->addError($object, $attribute, $this->message);
- }
- } else {
- if (!$this->strict && $value != $this->requiredValue || $this->strict && $value !== $this->requiredValue) {
+ } else {
$this->addError($object, $attribute, $this->message, array(
'{requiredValue}' => $this->requiredValue,
));
@@ -99,27 +106,24 @@ class RequiredValidator extends Validator
*/
public function clientValidateAttribute($object, $attribute)
{
+ $options = array();
if ($this->requiredValue !== null) {
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
+ $options['message'] = strtr($this->message, array(
'{requiredValue}' => $this->requiredValue,
));
- return "
-if (value != " . json_encode($this->requiredValue) . ") {
- messages.push(" . json_encode($message) . ");
-}
-";
+ $options['requiredValue'] = $this->requiredValue;
} else {
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
- ));
- return "
-if($.trim(value) == '') {
- messages.push(" . json_encode($message) . ");
-}
-";
+ $options['message'] = $this->message;
+ }
+ if ($this->strict) {
+ $options['strict'] = 1;
}
+
+ $options['message'] = Html::encode(strtr($options['message'], array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ )));
+
+ return 'yii.validation.required(value, messages, ' . json_encode($options) . ');';
}
}
diff --git a/framework/validators/StringValidator.php b/framework/validators/StringValidator.php
index 8b8c73b..5d0fa1a 100644
--- a/framework/validators/StringValidator.php
+++ b/framework/validators/StringValidator.php
@@ -8,6 +8,7 @@
namespace yii\validators;
use Yii;
+use yii\helpers\Html;
/**
* StringValidator validates that the attribute value is of certain length.
@@ -132,56 +133,42 @@ class StringValidator extends Validator
$label = $object->getAttributeLabel($attribute);
$value = $object->$attribute;
- $notEqual = strtr($this->notEqual, array(
- '{attribute}' => $label,
- '{value}' => $value,
- '{length}' => $this->is,
- ));
+ $options = array(
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $label,
+ '{value}' => $value,
+ ))),
+ );
- $tooShort = strtr($this->tooShort, array(
- '{attribute}' => $label,
- '{value}' => $value,
- '{min}' => $this->min,
- ));
-
- $tooLong = strtr($this->tooLong, array(
- '{attribute}' => $label,
- '{value}' => $value,
- '{max}' => $this->max,
- ));
-
- $js = '';
if ($this->min !== null) {
- $js .= "
-if(value.length< {$this->min}) {
- messages.push(" . json_encode($tooShort) . ");
-}
-";
+ $options['min'] = $this->min;
+ $options['tooShort'] = Html::encode(strtr($this->tooShort, array(
+ '{attribute}' => $label,
+ '{value}' => $value,
+ '{min}' => $this->min,
+ )));
}
if ($this->max !== null) {
- $js .= "
-if(value.length> {$this->max}) {
- messages.push(" . json_encode($tooLong) . ");
-}
-";
+ $options['max'] = $this->max;
+ $options['tooLong'] = Html::encode(strtr($this->tooLong, array(
+ '{attribute}' => $label,
+ '{value}' => $value,
+ '{max}' => $this->max,
+ )));
}
if ($this->is !== null) {
- $js .= "
-if(value.length!= {$this->is}) {
- messages.push(" . json_encode($notEqual) . ");
-}
-";
+ $options['is'] = $this->is;
+ $options['notEqual'] = Html::encode(strtr($this->notEqual, array(
+ '{attribute}' => $label,
+ '{value}' => $value,
+ '{length}' => $this->is,
+ )));
}
-
if ($this->skipOnEmpty) {
- $js = "
-if($.trim(value)!='') {
- $js
-}
-";
+ $options['skipOnEmpty'] = 1;
}
- return $js;
+ return 'yii.validation.string(value, messages, ' . json_encode($options) . ');';
}
}
diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php
index 2240e0a..7072ff4 100644
--- a/framework/validators/UniqueValidator.php
+++ b/framework/validators/UniqueValidator.php
@@ -97,4 +97,4 @@ class UniqueValidator extends Validator
$this->addError($object, $attribute, $this->message);
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/validators/UrlValidator.php b/framework/validators/UrlValidator.php
index cd6bfef..0ed59bd 100644
--- a/framework/validators/UrlValidator.php
+++ b/framework/validators/UrlValidator.php
@@ -8,6 +8,9 @@
namespace yii\validators;
use Yii;
+use yii\helpers\Html;
+use yii\helpers\JsExpression;
+use yii\helpers\Json;
/**
* UrlValidator validates that the attribute value is a valid http or https URL.
@@ -100,40 +103,27 @@ class UrlValidator extends Validator
*/
public function clientValidateAttribute($object, $attribute)
{
- $message = strtr($this->message, array(
- '{attribute}' => $object->getAttributeLabel($attribute),
- '{value}' => $object->$attribute,
- ));
-
if (strpos($this->pattern, '{schemes}') !== false) {
$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
} else {
$pattern = $this->pattern;
}
- $js = "
-if(!value.match($pattern)) {
- messages.push(" . json_encode($message) . ");
-}
-";
- if ($this->defaultScheme !== null) {
- $js = "
-if(!value.match(/:\\/\\//)) {
- value=" . json_encode($this->defaultScheme) . "+'://'+value;
-}
-$js
-";
- }
-
+ $options = array(
+ 'pattern' => new JsExpression($pattern),
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ ))),
+ );
if ($this->skipOnEmpty) {
- $js = "
-if($.trim(value)!='') {
- $js
-}
-";
+ $options['skipOnEmpty'] = 1;
+ }
+ if ($this->defaultScheme !== null) {
+ $options['defaultScheme'] = $this->defaultScheme;
}
- return $js;
+ return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');';
}
}
diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php
index 5ab8dfe..677191b 100644
--- a/framework/validators/Validator.php
+++ b/framework/validators/Validator.php
@@ -76,10 +76,11 @@ abstract class Validator extends Component
*/
public $attributes;
/**
- * @var string the user-defined error message. Error message may contain some placeholders
- * that will be replaced with the actual values by the validator.
- * The `{attribute}` and `{value}` are placeholders supported by all validators.
- * They will be replaced with the attribute label and value, respectively.
+ * @var string the user-defined error message. It may contain the following placeholders which
+ * will be replaced accordingly by the validator:
+ *
+ * - `{attribute}`: the label of the attribute being validated
+ * - `{value}`: the value of the attribute being validated
*/
public $message;
/**
@@ -100,12 +101,13 @@ abstract class Validator extends Component
* is null or an empty string.
*/
public $skipOnEmpty = true;
-
/**
- * @var boolean whether to enable client-side validation. Defaults to null, meaning
- * its actual value inherits from that of [[\yii\web\ActiveForm::enableClientValidation]].
+ * @var boolean whether to enable client-side validation for this validator.
+ * The actual client-side validation is done via the JavaScript code returned
+ * by [[clientValidateAttribute()]]. If that method returns null, even if this property
+ * is true, no client-side validation will be done by this validator.
*/
- public $enableClientValidation;
+ public $enableClientValidation = true;
/**
* Validates a single attribute.
@@ -211,7 +213,6 @@ abstract class Validator extends Component
* @param string $attribute the name of the attribute to be validated.
* @return string the client-side validation script. Null if the validator does not support
* client-side validation.
- * @see enableClientValidation
* @see \yii\web\ActiveForm::enableClientValidation
*/
public function clientValidateAttribute($object, $attribute)
diff --git a/framework/views/error.php b/framework/views/error.php
index 548d04b..009050a 100644
--- a/framework/views/error.php
+++ b/framework/views/error.php
@@ -64,4 +64,4 @@ $title = $context->htmlEncode($exception instanceof \yii\base\Exception ? $excep
versionInfo : ''?>